/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */


package org.apache.axis2.description;

import org.apache.axiom.om.OMElement;
import org.apache.axis2.AxisFault;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.i18n.Messages;
import org.apache.axis2.modules.Module;
import org.apache.axis2.util.Utils;

import javax.xml.namespace.QName;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * <p>This holds the information about a Module. </p>
 * <ol>
 * <li>parameters<li>
 * <li>handlers<li>
 * <ol>
 * <p>Handler are registered once they are available. They are available to all services if axis2.xml
 * has a module ref="." or available to a single service if services.xml have module ref=".."</p>
 */
public class AxisModule implements ParameterInclude {

    /**
     * Field flowInclude
     */
    private final FlowInclude flowInclude = new FlowInclude();

    /**
     * Field parameters
     */
    private final ParameterInclude parameters = new ParameterIncludeImpl();
    private Module module;
    private ClassLoader moduleClassLoader;
    // To keep the File that module came from
    private URL fileName;

    /**
     * Field name
     */
    private String name;

    //This is to keep the version number of the module, if the module name is a-b-c-1.3.mar ,
    // then the module version would be 1.3
    private Version version;

    // to store module operations , which are suppose to be added to a service if it is engaged to a service
    private HashMap<QName, AxisOperation> operations = new HashMap<QName, AxisOperation>();
    private AxisConfiguration parent;

    /*
    * to store policies which are valid for any service for which the module is engaged
    */
    private PolicyInclude policyInclude = null;

    // Small description about the module
    private String moduleDescription;

    private String[] supportedPolicyNames;

    private QName[] localPolicyAssertions;
    public static final String VERSION_SNAPSHOT = "SNAPSHOT";
    public static final String MODULE_SERVICE = "moduleService";

    private PolicySubject policySubject = new PolicySubject();

    /**
     * Constructor ModuleDescription.
     */
    public AxisModule() {
    }

    /**
     * Constructor ModuleDescription.
     *
     * @param name : Name of the module
     */
    public AxisModule(String name) {
        this.name = name;
    }

    /**
     * Get the name of this module.
     * Note that it is possible to deploy several versions of the same module. Therefore,
     * the name of a module is not unique in the scope of a given {@link AxisConfiguration}.
     * 
     * @return the name of the module
     */
    public String getName() {
        return name;
    }

    /**
     * Set the name of this module.
     * 
     * @param name the name of the module
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Get the archive name of this module. The archive name is the combination
     * of the module name and version (if available). In general it is equal to the
     * name of the module archive file without the suffix.
     * 
     * @return the archive name of the module
     */
    public String getArchiveName() {
        return version == null ? name : (name + "-" + version);
    }

    /**
     * Set the archive name of this module. This method will split the archive name
     * to extract the module name and version (which can be retrieved using
     * {@link #getName()} and {@link #getVersion()}).
     * 
     * @param archiveName the archive name of the module
     */
    public void setArchiveName(String archiveName) {
        int index = 0;
        // First look for a part that starts with a digit
        while ((index = archiveName.indexOf('-', index)) != -1) {
            char c = archiveName.charAt(++index);
            if ('0' <= c && c <= '9') {
                break;
            }
        }
        // Also support SNAPSHOT
        if (index == -1 && archiveName.endsWith("-SNAPSHOT")) {
            index = archiveName.length()-8;
        }
        
        if (index == -1) {
            name = archiveName;
            version = null;
        } else {
            try {
                version = new Version(archiveName.substring(index));
            } catch (ParseException ex) {
                version = null;
            }
            if (version == null) {
                name = archiveName;
            } else {
                name = archiveName.substring(0, index-1);
            }
        }
    }

    /**
     * Get the version of this module.
     * 
     * @return the version of the module, or <code>null</code> if the module doesn't have a
     *         version number
     */
    public Version getVersion() {
        return version;
    }

    /**
     * Set the version of this module.
     * 
     * @param version the version of the module
     */
    public void setVersion(Version version) {
        this.version = version;
    }

    public void addOperation(AxisOperation axisOperation) {
        operations.put(axisOperation.getName(), axisOperation);
    }

    /**
     * @param param : Parameter to be added
     */
    public void addParameter(Parameter param) throws AxisFault {
        if (isParameterLocked(param.getName())) {
            throw new AxisFault(Messages.getMessage("paramterlockedbyparent", param.getName()));
        } else {
            parameters.addParameter(param);
        }
    }

    public void removeParameter(Parameter param) throws AxisFault {
        if (isParameterLocked(param.getName())) {
            throw new AxisFault(Messages.getMessage("paramterlockedbyparent", param.getName()));
        } else {
            parameters.removeParameter(param);
        }
    }

    public void deserializeParameters(OMElement parameterElement) throws AxisFault {
        this.parameters.deserializeParameters(parameterElement);
    }

    /**
     * @return Returns Flow.
     */
    public Flow getFaultInFlow() {
        return flowInclude.getFaultInFlow();
    }

    public Flow getFaultOutFlow() {
        return flowInclude.getFaultOutFlow();
    }

    /**
     * @return Returns Flow.
     */
    public Flow getInFlow() {
        return flowInclude.getInFlow();
    }

    /**
     * @return Returns Module.
     */
    public Module getModule() {
        return module;
    }

    public ClassLoader getModuleClassLoader() {
        return moduleClassLoader;
    }

    public HashMap<QName, AxisOperation> getOperations() {
        return operations;
    }

    /**
     * @return Returns Flow.
     */
    public Flow getOutFlow() {
        return flowInclude.getOutFlow();
    }

    /**
     * @return Returns Parameter.
     */
    public Parameter getParameter(String name) {
        return parameters.getParameter(name);
    }

    public ArrayList<Parameter> getParameters() {
        return parameters.getParameters();
    }

    public AxisConfiguration getParent() {
        return parent;
    }

    // to check whether a given parameter is locked
    public boolean isParameterLocked(String parameterName) {

        // checking the locked value of parent
        boolean loscked = false;

        if (this.parent != null) {
            loscked = this.parent.isParameterLocked(parameterName);
        }

        if (loscked) {
            return true;
        } else {
            Parameter parameter = getParameter(parameterName);

            return (parameter != null) && parameter.isLocked();
        }
    }

    /**
     * @param faultFlow : Arryalist of handlerDescriptions
     */
    public void setFaultInFlow(Flow faultFlow) {
        flowInclude.setFaultInFlow(faultFlow);
    }

    /**
     * @param faultFlow : Arryalist of HandlerDescriptions
     */
    public void setFaultOutFlow(Flow faultFlow) {
        flowInclude.setFaultOutFlow(faultFlow);
    }

    public void setInFlow(Flow inFlow) {
        flowInclude.setInFlow(inFlow);
    }

    /**
     * @param module : AxisModule
     */
    public void setModule(Module module) {
        this.module = module;
    }

    public void setModuleClassLoader(ClassLoader moduleClassLoader) {
        this.moduleClassLoader = moduleClassLoader;
    }

    public void setOutFlow(Flow outFlow) {
        flowInclude.setOutFlow(outFlow);
    }

    public void setParent(AxisConfiguration parent) {
        this.parent = parent;
    }

    public void setPolicyInclude(PolicyInclude policyInclude) {
        this.policyInclude = policyInclude;
    }

    public PolicyInclude getPolicyInclude() {
        if (policyInclude == null) {
            policyInclude = new PolicyInclude();
        }
        return policyInclude;
    }
    
    public PolicySubject getPolicySubject() {
    	return policySubject;
    }

    public String getModuleDescription() {
        return moduleDescription;
    }

    public void setModuleDescription(String moduleDescription) {
        this.moduleDescription = moduleDescription;
    }

    public String[] getSupportedPolicyNamespaces() {
        return supportedPolicyNames;
    }

    public void setSupportedPolicyNamespaces(String[] supportedPolicyNamespaces) {
        this.supportedPolicyNames = supportedPolicyNamespaces;
    }

    public QName[] getLocalPolicyAssertions() {
        return localPolicyAssertions;
    }

    public void setLocalPolicyAssertions(QName[] localPolicyAssertions) {
        this.localPolicyAssertions = localPolicyAssertions;
    }

    public URL getFileName() {
        return fileName;
    }

    public void setFileName(URL fileName) {
        this.fileName = fileName;
    }
}
