/*
 *  Copyright (c) 2005-2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. 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.wso2.carbon.governance.api.common;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMText;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.governance.api.common.dataobjects.GovernanceArtifact;
import org.wso2.carbon.governance.api.common.dataobjects.GovernanceArtifactImpl;
import org.wso2.carbon.governance.api.exception.GovernanceException;
import org.wso2.carbon.governance.api.generic.dataobjects.GenericArtifact;
import org.wso2.carbon.governance.api.util.GovernanceArtifactConfiguration;
import org.wso2.carbon.governance.api.util.GovernanceUtils;
import org.wso2.carbon.registry.common.TermData;
import org.wso2.carbon.registry.core.Association;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.pagination.Paginate;
import org.wso2.carbon.registry.core.utils.RegistryUtils;
import org.wso2.carbon.registry.extensions.utils.CommonConstants;

import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;

/**
 * Base Manager Functionality which can be used by any Artifact Manager instance.
 */
@SuppressWarnings("unused")
public class GovernanceArtifactManager {

    private static final Log log = LogFactory.getLog(GovernanceArtifactManager.class);
    private Registry registry;
    private String mediaType;
    private String artifactNameAttribute;
    private String artifactNamespaceAttribute;
    private String artifactElementRoot;
    private String artifactElementNamespace;
    private String pathExpression;
    private String lifecycle;
    private List<Association> relationshipDefinitions;
    private List<Map> validationAttributes;

    /**
     * Constructor accepting an instance of the registry, and also details on the type of manager.
     *
     * @param registry                   the instance of the registry.
     * @param mediaType                  the media type of resources being saved or fetched.
     * @param artifactNameAttribute      the attribute that specifies the name of the artifact.
     * @param artifactNamespaceAttribute the attribute that specifies the namespace of the artifact.
     * @param artifactElementRoot        the attribute that specifies the root artifact element.
     * @param artifactElementNamespace   the attribute that specifies the artifact element's
     *                                   namespace.
     * @param pathExpression             the expression that can be used to compute where to store
     *                                   the artifact.
     * @param relationshipDefinitions    the relationship definitions for the types of associations
     *                                   that will be created when the artifact gets updated.
     */
    public GovernanceArtifactManager(Registry registry, String mediaType,
                                     String artifactNameAttribute,
                                     String artifactNamespaceAttribute, String artifactElementRoot,
                                     String artifactElementNamespace, String pathExpression,
                                     Association[] relationshipDefinitions) {
        this.registry = registry;
        this.mediaType = mediaType;
        this.artifactNameAttribute = artifactNameAttribute;
        this.artifactNamespaceAttribute = artifactNamespaceAttribute;
        this.artifactElementRoot = artifactElementRoot;
        this.artifactElementNamespace = artifactElementNamespace;
        this.pathExpression = pathExpression;
        this.relationshipDefinitions = Arrays.asList(relationshipDefinitions);
    }

    /**
     * Constructor accepting an instance of the registry, and also details on the type of manager.
     *
     * @param registry                   the instance of the registry.
     * @param mediaType                  the media type of resources being saved or fetched.
     * @param artifactNameAttribute      the attribute that specifies the name of the artifact.
     * @param artifactNamespaceAttribute the attribute that specifies the namespace of the artifact.
     * @param artifactElementRoot        the attribute that specifies the root artifact element.
     * @param artifactElementNamespace   the attribute that specifies the artifact element's
     *                                   namespace.
     * @param pathExpression             the expression that can be used to compute where to store
     *                                   the artifact.
     * @param lifecycle                  the lifecycle name which associated with the artifacts
     * @param validationAttributes       the validations for artifact attributes
     * @param relationshipDefinitions    the relationship definitions for the types of associations
     *                                   that will be created when the artifact gets updated.
     *
     */
    public GovernanceArtifactManager(Registry registry, String mediaType,
                                     String artifactNameAttribute,
                                     String artifactNamespaceAttribute, String artifactElementRoot,
                                     String artifactElementNamespace, String pathExpression,
                                     String lifecycle, List<Map> validationAttributes,
                                     Association[] relationshipDefinitions) {
        this.registry = registry;
        this.mediaType = mediaType;
        this.artifactNameAttribute = artifactNameAttribute;
        this.artifactNamespaceAttribute = artifactNamespaceAttribute;
        this.artifactElementRoot = artifactElementRoot;
        this.artifactElementNamespace = artifactElementNamespace;
        this.pathExpression = pathExpression;
        this.lifecycle = lifecycle;
        this.validationAttributes = validationAttributes;
        this.relationshipDefinitions = Arrays.asList(relationshipDefinitions);
    }

    /**
     * Creates a new artifact from the given qualified name.
     *
     * @return the artifact added.
     * @throws GovernanceException if the operation failed.
     */
    public GovernanceArtifact newGovernanceArtifact() throws GovernanceException {
        List<String> uniqueAttributes = GovernanceUtils.getUniqueAttributesNames(registry, mediaType);
        return GovernanceArtifactImpl.create(registry, UUID.randomUUID().toString(), uniqueAttributes);
    }

    /**
     * Creates a new artifact from the given content.
     *
     * @param content the artifact content.
     *
     * @return the artifact added.
     * @throws GovernanceException if the operation failed.
     */
    public GovernanceArtifact newGovernanceArtifact(OMElement content) throws GovernanceException {
        List<String> uniqueAttributes = GovernanceUtils.getUniqueAttributesNames(registry, mediaType);
        return GovernanceArtifactImpl.create(registry, UUID.randomUUID().toString(), content, uniqueAttributes);
    }

    /**
     * Adds the given artifact to the registry. Please do not use this method to update an existing
     * artifact use the update method instead. If this method is used to update an existing
     * artifact, all existing properties (such as lifecycle details) will be removed from the
     * existing artifact.
     *
     * @param artifact the artifact.
     *
     * @throws GovernanceException if the operation failed.
     */
    public void addGovernanceArtifact(GovernanceArtifact artifact) throws GovernanceException {
        // adding the attributes for name, namespace + artifact
        if (artifact.getQName() == null || artifact.getQName().getLocalPart() == null) {
            String msg = "A valid qualified name was not set for this artifact";
            log.error(msg);
            throw new GovernanceException(msg);
        }
        String artifactName = artifact.getQName().getLocalPart();
        if (artifactNameAttribute != null) {
            if (StringUtils.isNotEmpty(artifactName)) {
                artifact.setAttributes(artifactNameAttribute, new String[]{artifactName});
            }
        }
        String namespace = artifact.getQName().getNamespaceURI();
        if (artifactNamespaceAttribute != null && StringUtils.isNotEmpty(namespace)) {
            artifact.setAttributes(artifactNamespaceAttribute, new String[]{namespace});
        } else if (artifactNamespaceAttribute != null) {
            namespace = artifact.getAttribute(artifactNamespaceAttribute);
        }
        setQName(artifact, artifactName, namespace);

        validateArtifact(artifact);

        ((GovernanceArtifactImpl)artifact).associateRegistry(registry);
        boolean succeeded = false;
        Resource resource = null;
        String path = null;
        try {
            registry.beginTransaction();
            resource = registry.newResource();

            resource.setMediaType(mediaType);
            setContent(artifact, resource);
            // the artifact will not actually stored in the tmp path.
            path = GovernanceUtils.getPathFromPathExpression(
                    pathExpression, artifact);

            if(registry.resourceExists(path)){
                throw new GovernanceException("Governance artifact " + artifactName + " already exists at " + path);
            }

            String artifactId = artifact.getId();
            resource.setUUID(artifactId);
            registry.put(path, resource);
            String updatedPath = GovernanceUtils.getArtifactPath(registry, artifactId);
            if(updatedPath != null && !path.equals(updatedPath)){
                path = updatedPath;
            }
            if (lifecycle != null){
                String[] lifeCycles = lifecycle.split(",");
                ArrayUtils.reverse(lifeCycles);
                for (String attachingLifeCycle : lifeCycles) {
                    if (StringUtils.isNotEmpty(attachingLifeCycle)) {
                        registry.associateAspect(path, attachingLifeCycle);
                    }
                }
            }

            ((GovernanceArtifactImpl)artifact).updatePath();
//            artifact.setId(resource.getUUID()); //This is done to get the UUID of a existing resource.
            addRelationships(path, artifact);

            succeeded = true;
        }
        catch (RegistryException e) {
            String msg;
            if (artifact.getPath() != null) {
                msg = "Failed to add artifact: artifact id: " + artifact.getId() +
                        ", path: " + artifact.getPath() + ". " + e.getMessage();
            } else {
                msg = "Failed to add artifact: artifact id: " + artifact.getId() +
                        ". " + e.getMessage();
            }
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        } finally {
            if (succeeded) {
                try {
                    registry.commitTransaction();
                } catch (RegistryException e) {
                    String msg ;
                    if (artifact.getPath() != null) {
                        msg = "Error in committing transactions. Failed to add artifact: artifact " +
                                "id: " + artifact.getId() + ", path: " + artifact.getPath() + ".";
                    } else {
                        msg = "Error in committing transactions. Failed to add artifact: artifact " +
                                "id: " + artifact.getId() + ".";
                    }
                    log.error(msg, e);
                }
            } else {
                try {
                    registry.rollbackTransaction();
                } catch (RegistryException e) {
                    String msg =
                            "Error in rolling back transactions. Failed to add artifact: " +
                                    "artifact id: " + artifact.getId() + ", path: " +
                                    artifact.getPath() + ".";
                    log.error(msg, e);
                }
            }
        }
    }

    private void setQName(GovernanceArtifact artifact, String artifactName, String namespace) throws GovernanceException {
        if (StringUtils.isNotEmpty(artifactNamespaceAttribute) && StringUtils.isNotEmpty(artifactNameAttribute)) {
            QName qname = new QName(namespace, artifactName);
            artifact.setQName(qname);
        } else if (StringUtils.isNotEmpty(artifactNameAttribute)) {
            QName qname = new QName(artifactName);
            artifact.setQName(qname);
        }
    }

    private void addRelationships(String path, GovernanceArtifact artifact)
            throws RegistryException {
        Map<String, AssociationInteger> typeMap =
                new LinkedHashMap<String, AssociationInteger>();
        for (Association relationship : relationshipDefinitions) {
            String type = relationship.getAssociationType();
            String source = relationship.getSourcePath();
            String target = relationship.getDestinationPath();
            if (typeMap.containsKey(type)) {
                AssociationInteger associationInteger = typeMap.get(type);
                if (source == null) {
                    if (associationInteger.getInteger() < 0) {
                        associationInteger.setInteger(0);
                    }
                    for (String targetPath :
                            GovernanceUtils.getPathsFromPathExpression(target, artifact)) {
                        if (registry.resourceExists(targetPath)) {
                            associationInteger.getAssociations().add(new Association(path, targetPath, type));
                        } else {
                            if (log.isDebugEnabled()) {
                                log.debug("Can not add association. Resource does not exist at" + targetPath);
                            }
                        }
                    }
                } else if (target == null) {
                    if (associationInteger.getInteger() > 0) {
                        associationInteger.setInteger(0);
                    }
                    for (String sourcePath :
                            GovernanceUtils.getPathsFromPathExpression(source, artifact)) {
                        if (registry.resourceExists(sourcePath)) {
                            associationInteger.getAssociations().add(new Association(sourcePath, path, type));
                        } else {
                            if (log.isDebugEnabled()) {
                                log.debug("Can not add association. Resource does not exist at" + sourcePath);
                            }
                        }
                    }
                }
            } else {
                AssociationInteger associationInteger = new AssociationInteger();
                if (source == null) {
                    associationInteger.setInteger(1);
                    for (String targetPath :
                            GovernanceUtils.getPathsFromPathExpression(target, artifact)) {
                        if (registry.resourceExists(targetPath)) {
                            associationInteger.getAssociations().add(new Association(path, targetPath, type));
                        } else {
                            if (log.isDebugEnabled()) {
                                log.debug("Can not add association. Resource does not exist at" + targetPath);
                            }
                        }
                    }
                } else if (target == null) {
                    associationInteger.setInteger(-1);
                    for (String sourcePath :
                            GovernanceUtils.getPathsFromPathExpression(source, artifact)) {
                        if (registry.resourceExists(sourcePath)) {
                            associationInteger.getAssociations().add(new Association(sourcePath, path, type));
                        } else {
                            if (log.isDebugEnabled()) {
                                log.debug("Can not add association. Resource does not exist at" + sourcePath);
                            }
                        }
                    }
                }
                typeMap.put(type, associationInteger);
            }
        }
        for (Map.Entry<String, AssociationInteger> e : typeMap.entrySet()) {
            AssociationInteger value = e.getValue();
            List<Association> associations = value.getAssociations();
            fixAssociations(path, e.getKey(), value.getInteger() >= 0, value.getInteger() <= 0,
                    associations.toArray(new Association[associations.size()]));
        }
    }

    private void fixAssociations(String path, String type, boolean isSource, boolean isTarget,
                                 Association[] toAdd)
            throws RegistryException {
        final String SEPARATOR = ":";
        // Get the existing association list which is related to the current operation
        Set<String> existingSet = new HashSet<String>();
        for (Association association : registry.getAllAssociations(path)) {
            if (type.equals(association.getAssociationType()) &&
                    ((isSource && association.getSourcePath().equals(path)) ||
                            (isTarget && association.getDestinationPath().equals(path)))) {
                existingSet.add(association.getSourcePath() + SEPARATOR +
                        association.getDestinationPath() +
                        SEPARATOR + association.getAssociationType());
            }
        }

        // Get the updated association list from the projectGroup object
        Set<String> updatedSet = new HashSet<String>();
        for (Association association : toAdd) {
            updatedSet.add(association.getSourcePath() + SEPARATOR +
                    association.getDestinationPath() +
                    SEPARATOR + association.getAssociationType());
        }

        Set<String> removedAssociations = new HashSet<String>(existingSet);
        removedAssociations.removeAll(updatedSet);

        Set<String> newAssociations = new HashSet<String>(updatedSet);
        newAssociations.removeAll(existingSet);

        for (String removedAssociation : removedAssociations) {
            String[] params = removedAssociation.split(SEPARATOR);
            registry.removeAssociation(params[0], params[1], params[2]);
        }

        for (String newAssociation : newAssociations) {
            String[] params = newAssociation.split(SEPARATOR);
            registry.addAssociation(params[0], params[1], params[2]);
        }
    }

    /**
     * Updates the given artifact on the registry.
     * @param artifact the artifact.
     *
     * @throws GovernanceException if the operation failed.
     */
    public void updateGovernanceArtifact(GovernanceArtifact artifact) throws GovernanceException {
        boolean succeeded = false;
        try {
            registry.beginTransaction();

            String artifactName = artifact.getQName().getLocalPart();
            if (artifactNameAttribute != null) {
                if (StringUtils.isNotEmpty(artifactName)) {
                    artifact.setAttributes(artifactNameAttribute, new String[]{artifactName});
                } else {
                    artifactName = artifact.getAttribute(artifactNameAttribute);
                }
            }
            String namespace = artifact.getQName().getNamespaceURI();
            if (artifactNamespaceAttribute != null && StringUtils.isNotEmpty(namespace)) {
                artifact.setAttributes(artifactNamespaceAttribute, new String[]{namespace});
            } else if (artifactNamespaceAttribute != null) {
                namespace = artifact.getAttribute(artifactNamespaceAttribute);
            }
            setQName(artifact, artifactName, namespace);
            validateArtifact(artifact);

            GovernanceArtifact oldArtifact = getGovernanceArtifact(artifact.getId());
            // first check for the old artifact and remove it.
            String oldPath = null;
            boolean needsToMove = false;
            if (oldArtifact != null) {
                QName oldName = oldArtifact.getQName();
                if (!oldName.equals(artifact.getQName())) {
                    String temp = oldArtifact.getPath();
                    // then it is analogue to moving the resource for the new location
                    // so just delete the old path
                    needsToMove = true;
                    //registry.delete(temp);
                } else {
                    oldPath = oldArtifact.getPath();
                }
            } else {
                throw new GovernanceException("No artifact found for the artifact id :" + artifact.getId() + ".");
            }

            String artifactId =  artifact.getId();
            Resource resource = registry.newResource();
            resource.setMediaType(mediaType);
            setContent(artifact, resource);

            String path;
            if (oldPath != null) {
                path = oldPath;
            } else {
                path = GovernanceUtils.getPathFromPathExpression(pathExpression, artifact);
            }
            if (needsToMove){
                if (isInbuiltMediatype()) {
                    registry.delete(oldArtifact.getPath());
                } else if (!oldArtifact.getPath().equals(path)) {
                    registry.move(oldArtifact.getPath(), path);
                }

            }

            if (registry.resourceExists(path)) {
                Resource oldResource = registry.get(path);
                Properties properties = (Properties) oldResource.getProperties().clone();

                // first clone existing properties
                resource.setProperties(properties);

                // then set updated properties
                // Stop the attributes been added as properties
                /*String[] attributeKeys = artifact.getAttributeKeys();
                if (attributeKeys != null) {
                    for (String aggregatedKey : attributeKeys) {
                        if (!aggregatedKey.equals(artifactNameAttribute) &&
                                !aggregatedKey.equals(artifactNamespaceAttribute)) {
                            resource.setProperty(aggregatedKey,
                                    Arrays.asList(artifact.getAttributes(aggregatedKey)));
                        }
                    }
                }*/

                //persisting resource description at artifact update
                String description = oldResource.getDescription();
                if(description != null) {
                    resource.setDescription(description);
                }

                String oldContent;
                Object content = oldResource.getContent();
                if (content instanceof String) {
                    oldContent = (String) content;
                } else {
                    oldContent = new String((byte[]) content);
                }
                String newContent;
                content = resource.getContent();
                if (content instanceof String) {
                    newContent = (String) content;
                } else {
                    newContent = new String((byte[]) content);
                }
                if (newContent.equals(oldContent)) {
                    artifact.setId(oldResource.getUUID());
                    addRelationships(path, artifact);
                    succeeded = true;
                    return;
                }
            }
            resource.setUUID(artifactId);
            registry.put(path, resource);
//            artifact.setId(resource.getUUID()); //This is done to get the UUID of a existing resource.
            addRelationships(path, artifact);
            ((GovernanceArtifactImpl)artifact).updatePath(artifactId);
            succeeded = true;
        } catch (RegistryException e) {
            if (e instanceof GovernanceException) {
                throw (GovernanceException) e;
            }
            String msg;
            if (artifact.getPath() != null) {
                msg = "Error in updating the artifact, artifact id: " + artifact.getId() +
                        ", artifact path: " + artifact.getPath() + "." + e.getMessage() + ".";
            } else {
                msg = "Error in updating the artifact, artifact id: " + artifact.getId() +
                        "." + e.getMessage() + ".";
            }
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        } finally {
            if (succeeded) {
                try {
                    registry.commitTransaction();
                } catch (RegistryException e) {
                    String msg;
                    if (artifact.getPath() != null) {
                        msg = "Error in committing transactions. Update artifact failed: artifact " +
                                "id: " + artifact.getId() + ", path: " + artifact.getPath() + ".";
                    } else {
                        msg = "Error in committing transactions. Update artifact failed: artifact " +
                                "id: " + artifact.getId() + ".";
                    }

                    log.error(msg, e);
                }
            } else {
                try {
                    registry.rollbackTransaction();
                } catch (RegistryException e) {
                    String msg =
                            "Error in rolling back transactions. Update artifact failed: " +
                                    "artifact id: " + artifact.getId() + ", path: " +
                                    artifact.getPath() + ".";
                    log.error(msg, e);
                }
            }
        }
    }

    private boolean isInbuiltMediatype() {
        return CommonConstants.REST_SERVICE_MEDIA_TYPE.equals(mediaType) ||
                CommonConstants.SOAP_SERVICE_MEDIA_TYPE.equals(mediaType) || CommonConstants.SERVICE_MEDIA_TYPE.equals(
                mediaType);
    }

    /**
     * Fetches the given artifact on the registry.
     *
     * @param artifactId the identifier of the artifact.
     *
     * @return the artifact.
     * @throws GovernanceException if the operation failed.
     */
    public GovernanceArtifact getGovernanceArtifact(String artifactId) throws GovernanceException {
        return GovernanceUtils.retrieveGovernanceArtifactById(registry, artifactId);
    }

    /**
     * Removes the given artifact from the registry.
     *
     * @param artifactId the identifier of the artifact.
     *
     * @throws GovernanceException if the operation failed.
     */
    public void removeGovernanceArtifact(String artifactId) throws GovernanceException {
        GovernanceUtils.removeArtifact(registry, artifactId);
    }

    /**
     * Sets content of the given artifact to the given resource on the registry.
     *
     * @param artifact the artifact.
     * @param resource the content resource.
     * @throws GovernanceException if the operation failed.
     */
    public void setContent(Resource resource, GovernanceArtifact artifact) throws
            GovernanceException {
        this.setContent(artifact, resource);
    }

    /**
     * Sets content of the given artifact to the given resource on the registry.
     *
     * @param artifact the artifact.
     * @param resource the content resource.
     *
     * @throws GovernanceException if the operation failed.
     */
    protected void setContent(GovernanceArtifact artifact, Resource resource) throws
            GovernanceException {
        try {
            if (artifact instanceof GenericArtifact) {
                Object content = ((GenericArtifact) artifact).getContent();
                if (content != null) {
                    setContentAndProperties(artifact, resource, content);
                    return;
                }
            }
            
            OMNamespace namespace = OMAbstractFactory.getOMFactory().createOMNamespace(artifactElementNamespace, "");
            Map<String, HashMap> mainElementMap = new HashMap<String, HashMap>();
            final String defaultUUID = UUID.randomUUID().toString();
            String[] attributeKeys = artifact.getAttributeKeys();
            if (attributeKeys != null) {
                for (String aggregatedKey : attributeKeys) {
                    String[] keys = aggregatedKey.split("_");
                    String key = null;
                    for (int i = 0; i < keys.length; i++) {
                        key = keys[i];
                        if (mainElementMap.get(key) == null) {
                            mainElementMap.put(key, new HashMap<String, String[]>());
                        }

                        // Handing the situations where we don't have '_' in aggregatedKey
                        // and assume we hare having only one '_" in aggregatedKey and not more
                        if (keys.length > 1) {
                            break;
                        }
                    }
                    String[] attributeValues = artifact.getAttributes(aggregatedKey);
                    String elementName = keys[keys.length - 1];
                    if (keys.length > 1) {
                        mainElementMap.get(key).put(elementName, attributeValues);
                    } else {
                        mainElementMap.get(key).put(defaultUUID, attributeValues);
                    }
                }
            	
            }
            
            OMElement contentElement = OMAbstractFactory.getOMFactory().createOMElement(artifactElementRoot, namespace);
            
            Iterator it = mainElementMap.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<String, HashMap> pairs = (Map.Entry)it.next();
                
                Map<String, Map>  subElementMap = pairs.getValue();
                Iterator subit = subElementMap.entrySet().iterator();
                int size = 0;
                boolean isOptionText =  false;
                while (subit.hasNext()) {
                    Map.Entry<String, String[]> subpairs = (Map.Entry) subit.next();
                    if (size < subpairs.getValue().length) {
                        size = subpairs.getValue().length;
                    }
                    // TODO : This is temporary fix for option-text related issue, removing option-text next major release.
                    if (subpairs.getKey().endsWith("entry")) {
                        isOptionText = true;
                    }
                }
                
                // TODO : This is temporary fix for option-text related issue, removing option-text next major release.
                if (isOptionText) {
                    OMElement keyElement = OMAbstractFactory.getOMFactory().createOMElement(pairs.getKey(), namespace);
                    for (int i = 0; i < size; i++) {
                        keyElement = getSubElementContent(subElementMap, namespace, defaultUUID, keyElement, i);
                    }
                    contentElement.addChild(keyElement);
                } else {
                    for (int i = 0; i < size; i++) {
                        OMElement keyElement = OMAbstractFactory.getOMFactory().createOMElement(pairs.getKey(), namespace);
                        keyElement = getSubElementContent(subElementMap, namespace, defaultUUID, keyElement, i);
                        contentElement.addChild(keyElement);
                    }
                }
            }
            String updatedContent = GovernanceUtils.serializeOMElement(contentElement);
            setContentAndProperties(artifact, resource, updatedContent);
            
        } catch (RegistryException e) {
            String msg;
            if (artifact.getPath() != null) {
                msg = "Error in saving attributes for the artifact. id: " + artifact.getId() +
                        ", path: " + artifact.getPath() + ".";
            } else {
                msg = "Error in saving attributes for the artifact. id: " + artifact.getId() + ".";
            }
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
    }

    private void setContentAndProperties(GovernanceArtifact artifact, Resource resource, Object content)
            throws RegistryException {
        resource.setContent(content);
        String[] propertyKeys = artifact.getPropertyKeys();
        boolean hasSourceProperty = false;
        if (propertyKeys != null) {
            for (String propertyKey : propertyKeys) {
                if ("resource.source".equals(propertyKey)) {
                    hasSourceProperty = true;
                }
                String[] propertyValues = artifact.getAttributes(propertyKey);
                resource.setProperty(propertyKey, Arrays.asList(propertyValues));
            }
        }
        if (!hasSourceProperty) {
            //TODO ERROR
            resource.setProperty("resource.source", "remote");
        }

        // Stop the attributes been added as properties
      /*  String[] attributeKeys = artifact.getAttributeKeys();
        if (attributeKeys != null) {
            for (String aggregatedKey : attributeKeys) {
                if (!aggregatedKey.equals(artifactNameAttribute) &&
                        !aggregatedKey.equals(artifactNamespaceAttribute)) {
                    resource.setProperty(aggregatedKey,
                            Arrays.asList(artifact.getAttributes(aggregatedKey)));
                }
            }
        }*/
    }


    /**
     * Finds all artifacts matching the given filter criteria.
     *
     * @param criteria the filter criteria to be matched.
     *
     * @return the artifacts that match.
     * @throws GovernanceException if the operation failed.
     */
    public GovernanceArtifact[] findGovernanceArtifacts(Map<String, List<String>> criteria)
            throws GovernanceException {
        List<GovernanceArtifact> artifacts;
        artifacts = GovernanceUtils.findGovernanceArtifacts(criteria != null ? criteria :
                Collections.<String, List<String>>emptyMap(), registry, mediaType);
        if (artifacts != null) {
            return artifacts.toArray(new GovernanceArtifact[artifacts.size()]);
        } else {
            return new GovernanceArtifact[0];
        }
    }

    /**
     * Find all possible terms and its count for the given facet field and query criteria
     * @param criteria the filter criteria to be matched
     * @param facetField field used for faceting
     * @param authRequired authorization required flag
     * @return term results
     * @throws GovernanceException
     */
    public TermData[] getTermData(Map<String, List<String>> criteria, String facetField, boolean authRequired) throws GovernanceException {
        List<TermData> termsList;
        termsList = GovernanceUtils.getTermDataList((criteria != null) ? criteria :
                Collections.<String, List<String>>emptyMap(), facetField, mediaType, authRequired);
        if (termsList != null) {
            return termsList.toArray(new TermData[termsList.size()]);
        } else {
            return new TermData[0];
        }
    }

    /**
     * Finds and returns all GovernanceArtifacts that match the search query.
     *
     * @param query The query to search artifacts
     * @return Array of artifacts that match the query string
     * @throws GovernanceException if the operation failed
     */
    public GovernanceArtifact[] findGovernanceArtifacts(String query) throws GovernanceException {
        List<GovernanceArtifact> artifacts;
        artifacts = GovernanceUtils.findGovernanceArtifacts(query, registry, mediaType);
        if (artifacts != null) {
            return artifacts.toArray(new GovernanceArtifact[artifacts.size()]);
        } else {
            return new GovernanceArtifact[0];
        }
    }

    /**
     * Finds all artifacts matching the given filter criteria.
     *
     * @param criteria the filter criteria to be matched.
     *
     * @return the artifacts that match.
     * @throws GovernanceException if the operation failed.
     */
    public GovernanceArtifact[] findGovernanceArtifacts(GovernanceArtifactFilter criteria)
            throws GovernanceException {
        List<GovernanceArtifact> artifacts = new ArrayList<GovernanceArtifact>();
        for (GovernanceArtifact artifact : getAllGovernanceArtifacts()) {
            if (artifact != null) {
                if (criteria.matches(artifact)) {
                    artifacts.add(artifact);
                }
            }
        }
        return artifacts.toArray(new GovernanceArtifact[artifacts.size()]);
    }

    /**
     * Finds all artifacts of a given type on the registry.
     *
     * @return all artifacts of the given type on the registry.
     * @throws GovernanceException if the operation failed.
     */
    public GovernanceArtifact[] getAllGovernanceArtifacts() throws GovernanceException {
        List<String> paths = getPaginatedGovernanceArtifacts();
        return getGovernanceArtifacts(paths.toArray(new String[paths.size()]));
    }

    @Paginate("getPaginatedGovernanceArtifacts")
    public List<String> getPaginatedGovernanceArtifacts() throws GovernanceException {
        List<String> paths =
                Arrays.asList(GovernanceUtils.getResultPaths(registry,
                        mediaType));
        Collections.sort(paths, new Comparator<String>() {
            public int compare(String o1, String o2) {
                Long l1 = -1l;
                Long l2 = -1l;

                String temp1 = RegistryUtils.getParentPath(o1);
                String temp2 = RegistryUtils.getParentPath(o2);
                try {
                    l1 = Long.parseLong(
                            RegistryUtils.getResourceName(temp1));
                    l2 = Long.parseLong(
                            RegistryUtils.getResourceName(temp2));
                } catch (NumberFormatException ignore) {

                }

                // First order by name
                int result = RegistryUtils.getResourceName(temp1).compareToIgnoreCase(
                        RegistryUtils.getResourceName(temp2));
                if (result != 0) {
                    return result;
                }
                // Then order by namespace
                result = temp1.compareToIgnoreCase(temp2);
                if (result != 0) {
                    return result;
                }
                // Finally by version
                return l2.compareTo(l1);
            }
        });
        return paths;
    }

    /**
     * Finds all identifiers of the artifacts on the registry.
     *
     * @return an array of identifiers of the artifacts.
     * @throws GovernanceException if the operation failed.
     */
    public String[] getAllGovernanceArtifactIds() throws GovernanceException {
        GovernanceArtifact[] artifacts = getAllGovernanceArtifacts();
        String[] artifactIds = new String[artifacts.length];
        for (int i = 0; i < artifacts.length; i++) {
            artifactIds[i] = artifacts[i].getId();
        }
        return artifactIds;
    }

    private static class AssociationInteger {

        private List<Association> associations = new LinkedList<Association>();
        private Integer integer;

        public List<Association> getAssociations() {
            return associations;
        }

        public Integer getInteger() {
            return integer;
        }

        public void setInteger(Integer integer) {
            this.integer = integer;
        }
    }

    /**
     * Retrieve all the governance artifacts which associated with the given lifecycle
     *
     * @param lcName Name of the lifecycle
     *
     * @return GovernanceArtifact array
     * @throws GovernanceException
     */
    public GovernanceArtifact[] getAllGovernanceArtifactsByLifecycle(String lcName) throws GovernanceException {
        String[] paths = GovernanceUtils.getAllArtifactPathsByLifecycle(registry, lcName, mediaType);
        if (paths == null) {
            return new GovernanceArtifact[0];
        }
        return getGovernanceArtifacts(paths);
    }

    /**
     * Retrieve all the governance artifacts which associated with the given lifecycle in the given lifecycle state
     *
     * @param lcName  Name of the lifecycle
     * @param lcState Name of the current lifecycle state
     *
     * @return GovernanceArtifact array
     * @throws GovernanceException
     */
    public GovernanceArtifact[] getAllGovernanceArtifactsByLIfecycleStatus(String lcName, String lcState)
            throws GovernanceException {
        String[] paths = GovernanceUtils.getAllArtifactPathsByLifecycleState(registry, lcName, lcState, mediaType);
        if (paths == null) {
            return new GovernanceArtifact[0];
        }
        return getGovernanceArtifacts(paths);
    }

    private void validateArtifact(GovernanceArtifact artifact)
            throws GovernanceException{
        if(validationAttributes == null){
            return;
        }
        Map<String, Object> map;
        for (int i=0; i<validationAttributes.size(); ++i) {
            map = validationAttributes.get(i);
            String value = "";
            String prop = (String)map.get("properties");
            List<String> keys = (List<String>)map.get("keys");

            if (prop != null && "unbounded".equals(prop)) {
                //assume there are only 1 key
                String[] values = artifact.getAttributes((String)keys.get(0));
                if (values != null) {
                    for (int j=0; j<values.length; ++j) {
                        if (map.containsKey("isMandatory") && (boolean)map.get("isMandatory") &&
                            (values[j] == null || "".equals(values[j]))) {
                            //return an exception to stop adding artifact
                            throw new GovernanceException((String) map.get("name") + " is a required field, " +
                                                          "Please provide a value for this parameter.");
                        }
                        if (map.containsKey("regexp") && !values[j].matches((String)map.get("regexp"))) {
                            //return an exception to stop adding artifact
                            throw new GovernanceException((String)map.get("name") + " doesn't match regex: " +
                                    (String)map.get("regexp"));
                        }
                    }
                }
            } else {
                for (int j=0; j<keys.size(); ++j) {
                    String v = artifact.getAttribute(keys.get(j));
                    if (j != 0) value += ":";
                    value += (v == null ? "" : v);
                }
                if (map.containsKey("isMandatory") && (boolean)map.get("isMandatory") &&
                    (value == null || "".equals(value))) {
                    //return an exception to stop adding artifact
                    throw new GovernanceException((String) map.get("name") + " is a required field, " +
                                                  "Please provide a value for this parameter.");
                }
                if (map.containsKey("regexp") && value != null && !value.equals("") &&
                    !value.matches((String)map.get("regexp"))) {
                    //return an exception to stop adding artifact
                    throw new GovernanceException((String)map.get("name") + " doesn't match regex: " +
                            (String)map.get("regexp"));
                }
            }
        }
    }
    
    private OMElement getSubElementContent (Map<String, Map> subElementMap, OMNamespace namespace, String defaultUUID, OMElement keyElement, int posotion) {
        Iterator iterator = subElementMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String[]> subpairs = (Map.Entry)iterator.next();
            String value ;
            try {
                value = subpairs.getValue()[posotion];
            } catch (Exception ex) {
                value = null;
            }

            //We have defaultUUID for non '_' scenario in this case remove defaultUUID and append it to the
            //parent element.
            if (subpairs.getKey().equals(defaultUUID)) {
                OMText textElement = OMAbstractFactory.getOMFactory().createOMText(value);
                keyElement.addChild(textElement);
            } else {
                OMElement subkeyElement = OMAbstractFactory.getOMFactory().createOMElement(subpairs.getKey(), namespace);
                OMText textElement = OMAbstractFactory.getOMFactory().createOMText(value);
                subkeyElement.addChild(textElement);
                keyElement.addChild(subkeyElement);
            } 
        }
        return keyElement;
    }

    /**
     * This method is used to fetch all the {@link GovernanceArtifact} for a given set of paths.
     * If there are exceptions in retrieving artifacts for a given path, we ignore those exceptions.
     * If all the paths failed due to errors, then we throw a {@link GovernanceException}
     *
     * @param paths Array of paths of the governance artifacts
     * @return Array of {@link GovernanceArtifact} for the given paths
     * @throws GovernanceException if fetching artifacts for all the given paths failed
     */
    private GovernanceArtifact[] getGovernanceArtifacts(String[] paths) throws GovernanceException {
        int errorCount = 0;

        List<GovernanceArtifact> artifactList = new ArrayList<GovernanceArtifact>();
        for (String path : paths) {
            try {
                artifactList.add(GovernanceUtils.retrieveGovernanceArtifactByPath(registry, path));
            } catch (GovernanceException e) {
                // We do not through any exception here. Only logging is done.
                // We increase the error count for each error. If all the paths failed, then we throw an error
                errorCount++;
                log.error("Error occurred while retrieving governance artifact by path : " + path, e);
            }
        }
        if (errorCount != 0 && errorCount == paths.length) {
            // This means that all the paths have failed. So we throw an error.
            throw new GovernanceException("Error occurred while retrieving all the governance artifacts");
        }
        return artifactList.toArray(new GovernanceArtifact[artifactList.size()]);
    }

    private boolean addDefaultAttributeIfNotExists(final GovernanceArtifact artifact, Resource resource, final String artifactName) throws GovernanceException {
        GovernanceArtifact[] governanceArtifacts = searchArtifactsByGroupingAttribute(artifact, mediaType, artifactName);

        if(governanceArtifacts != null && governanceArtifacts.length == 0) {
            resource.addProperty("default", "true");
            return true;
        }

        return false;
    }

    private void addDefaultAttributeToAssociations(final GovernanceArtifact artifact) throws GovernanceException {
        try {
            if(mediaType.equals("application/vnd.wso2-soap-service+xml")) {

                Association[] associations = registry.getAllAssociations(artifact.getPath());

                for(Association association : associations) {
                    String destinationPath = association.getDestinationPath();
                    if(destinationPath.contains("wsdl")) {
                        String[] subPaths = destinationPath.split("/");
                        final String artifactName = subPaths[subPaths.length - 1];
                        GovernanceArtifact[] governanceArtifacts = searchArtifactsByGroupingAttribute(artifact, CommonConstants.WSDL_MEDIA_TYPE, artifactName);

                        if(governanceArtifacts != null && governanceArtifacts.length == 0) {
                            Resource wsdlResource = registry.get(destinationPath);
                            wsdlResource.addProperty("default", "true");
                            registry.put(destinationPath, wsdlResource);
                        }
                    }
                }
            }
        } catch(RegistryException ex) {
            log.error("An error occurred while retrieving association of the resource " + artifact.getPath(), ex);
        }
    }

    private GovernanceArtifact[] searchArtifactsByGroupingAttribute(final GovernanceArtifact artifact, String mediaType, final String artifactName) throws GovernanceException {
        if(GovernanceUtils.getAttributeSearchService() == null) {
            return null;
        }

        Map<String, List<String>> listMap = new HashMap<String, List<String>>();

        GovernanceArtifactConfiguration artifactConfiguration ;
        String groupingAttribute = null ;

        try {
            artifactConfiguration = GovernanceUtils.getArtifactConfigurationByMediaType(registry, mediaType);

            if(artifactConfiguration != null) {
                groupingAttribute = artifactConfiguration.getGroupingAttribute();
            } else {
                log.debug("Artifact type with media type " + mediaType + " doesn't exist");
            }
        } catch(RegistryException ex) {
            log.error("An error occurred while retrieving the artifact configuration ", ex);
            return null;
        }

        if(groupingAttribute != null) {
            if(groupingAttribute.equals(CommonConstants.SERVICE_NAME_ATTRIBUTE)) {
                listMap.put(groupingAttribute, new ArrayList<String>() {{
                    add(artifactName);
                }});
            } else if(groupingAttribute.equals("overview_version")) {
                listMap.put(groupingAttribute, new ArrayList<String>() {{
                    add(artifact.getAttribute("overview_version"));
                }});
            }
        } else {
            listMap.put(CommonConstants.SERVICE_NAME_ATTRIBUTE, new ArrayList<String>() {{
                add(artifactName);
            }});
        }

        return findGovernanceArtifacts(listMap);
    }


    /**
     * Check whether GovernanceArtifact is exists in the Registry without loading whole artifact into memory.
     * This method only work for Configurable Governance Artifacts and doe not work for Content Artifacts such
     * as WSDL, WADL, Swagger, XMLSchema etc.
     *
     * @param artifact GovernanceArtifact to check it's existence.
     * @return true or false
     * @throws GovernanceException if the operation failed.
     */
    public boolean isExists(GovernanceArtifact artifact) throws GovernanceException {
        String path = GovernanceUtils.getPathFromPathExpression(
                pathExpression, artifact);
        try {
            return registry.resourceExists(path);
        } catch (RegistryException e) {
            throw new GovernanceException(e);
        }
    }

    public void removeGenericArtifact(GenericArtifact artifact) throws GovernanceException {
        String path = GovernanceUtils.getPathFromPathExpression(
                pathExpression, artifact);
        if (path != null) {
            try {
                GovernanceUtils.removeArtifactFromPath(registry, path);
            } catch (RegistryException e) {
                throw new GovernanceException(e);
            }
        }
    }



}
