/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2012 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.tenant.interceptor.jndi;

import org.ow2.carol.jndi.intercept.ContextInterceptor;
import org.ow2.carol.jndi.intercept.InterceptionContext;
import org.ow2.jonas.lib.tenant.context.TenantContext;
import org.ow2.jonas.lib.tenant.context.TenantCurrent;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Intercept calls and prefix JNDI names
 * @author Mohammed Boukada
 */
public class JNDITenantIdInterceptor implements ContextInterceptor {

    /**
     * Separator between tenant-id prefix and JNDI name
     */
    private final String separator;

    /**
     * Map containing tenants names and their corresponding tenant-id
     */
    private Map<String, List<String>> tenantNames = new HashMap<String, List<String>>();

    /**
     * Constructor
     * @param separator
     */
    public JNDITenantIdInterceptor(String separator) {
        this.separator = separator;
    }

    /**
     * Intercept call
     * @param interceptionContext
     * @return
     * @throws Exception
     */
    public Object intercept(InterceptionContext interceptionContext) throws Exception {
        String methodName = interceptionContext.getMethod().getName();
        Object[] parameters = interceptionContext.getParameters();
        boolean saveName = false;

        if (methodName.equals("bind")
                || methodName.equals("rebind")
                || methodName.equals("list")
                || methodName.equals("listBindings")
                || methodName.equals("destroySubcontext")
                || methodName.equals("createSubcontext")
                || methodName.equals("getNameParser")
                || methodName.equals("composeName")) {

            Object oldName = parameters[0];
            Object newName = null;

            // If is a bind operation, save the name
            if (methodName.equals("bind") || methodName.equals("rebind")) {
                saveName = true;
            }

            newName = updateName(oldName, saveName);

            if (newName != null) {
                parameters[0] = newName;
                interceptionContext.setParameters(parameters);
            }
        } else if (methodName.equals("lookup") || methodName.equals("lookupLink") || methodName.equals("unbind")) {
            // If is a lookup or unbind operation and the name was not bound by a tenant, don't update it
            Object oldName = parameters[0];
            Object newName = null;
            if (isTenantName(oldName)) {
                newName = getTenantId() + separator + oldName;
                parameters[0] = newName;
                interceptionContext.setParameters(parameters);
            }
        } else if (methodName.equals("rename")) {
            Object oldOldName = parameters[0];
            Object newOldName = null;
            if (isTenantName(oldOldName)){
                newOldName = updateName(oldOldName, saveName);
            }
            Object oldNewName = parameters[1];
            Object newNewName = null;
            if (newOldName != null) {
                newNewName = updateName(oldNewName, saveName);
            }
            if (newNewName != null && oldNewName != null) {
                parameters[0] = newOldName;
                parameters[1] = newNewName;
                interceptionContext.setParameters(parameters);
            }
        }
        return interceptionContext.proceed();
    }

    /**
     * Update JNDI name
     * @param old old name
     * @param saveName name bound from a tenant, save it
     * @return new name
     */
    private Object updateName(Object old, boolean saveName) {
        if (old == null || old.equals("") || isNameUpdated(old)) {
            return null;
        }

        String tenantId = getTenantId();
        if (tenantId != null && !tenantId.equals(TenantContext.DEFAULT_TENANT_ID)) {
            if (saveName) {
                if(tenantNames.get(tenantId) == null) {
                    List<String> list = new LinkedList<String>();
                    list.add(old.toString());
                    tenantNames.put(tenantId, list);
                } else {
                    List<String> list = tenantNames.get(tenantId);
                    list.add(old.toString());
                }
            }
            return tenantId + separator + old;
        }
        return null;
    }

    /**
     * Gets tenant-id from context
     * @return tenant-id
     */
    private String getTenantId() {
        String tenantId = null;
        if (TenantCurrent.getCurrent().getTenantContext() != null) {
            tenantId = TenantCurrent.getCurrent().getTenantContext().getTenantId();
        }
        return tenantId;
    }

    /**
     * Whether the name was already updated
     * @param name
     * @return
     */
    private boolean isNameUpdated(Object name) {
        return ((String) name).startsWith(getTenantId() + separator);
    }

    /**
     * Whether the name was bound by this tenant
     * @param name
     * @return
     */
    private boolean isTenantName(Object name) {
        String tenantId = getTenantId();
        if (tenantId == null) {
            return false;
        }
        List<String> list = tenantNames.get(tenantId);
        if (list == null) {
            return false;
        }
        return list.contains(name.toString());
    }
}
