/*
 * Copyright (c) 2008 - 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed 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.dataobjects;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMText;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.governance.api.common.util.ApproveItemBean;
import org.wso2.carbon.governance.api.common.util.CheckListItemBean;
import org.wso2.carbon.governance.api.exception.GovernanceException;
import org.wso2.carbon.governance.api.util.CheckpointTimeUtils;
import org.wso2.carbon.governance.api.util.GovernanceConstants;
import org.wso2.carbon.governance.api.util.GovernanceUtils;
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.session.UserRegistry;

import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Governance Artifact abstract class, This is overwritten by Endpoint, Policy, Schema, Service,
 * WSDL, People classes. This keeps common methods shared by all the governance artifacts
 */
@SuppressWarnings("unused")
public abstract class GovernanceArtifactImpl implements GovernanceArtifact {

    private static final Log log = LogFactory.getLog(GovernanceArtifactImpl.class);

    private String id;
    private String path;
    private Registry registry; // associated registry

    private String lcName;
    private String lcState;
    private String artifactPath;
    private List<String> uniqueAttributes;


    public List<String> getUniqueAttributes() {
        return uniqueAttributes;
    }

    public void setUniqueAttributes(List<String> uniqueAttributes) {
        this.uniqueAttributes = uniqueAttributes;
    }

    public String getLcName() {
        return lcName;
    }

    public void setLcName(String lcName) {
        this.lcName = lcName;
    }

    public String getLcState() {
        return lcState;
    }

    public void setLcState(String lcState) {
        this.lcState = lcState;
    }

    public String getArtifactPath() {
        return artifactPath;
    }

    public void setArtifactPath(String artifactPath) {
        this.artifactPath = artifactPath;
    }

    /**
     * Map of attributes associated with this governance artifact.
     */
    protected Map<String, List<String>> attributes = new HashMap<String, List<String>>();

    /**
     * Map of properties associated with this governance artifact.
     */
    protected Map<String, List<String>> properties = new HashMap<String, List<String>>();

    /**
     * Construct a governance artifact object from the path and the id.
     *
     * @param id the id
     */
    public GovernanceArtifactImpl(String id) {
        this.id = id;
    }

    /**
     * Construct a governance artifact. The default constructor.
     */
    public GovernanceArtifactImpl() {
        // the default constructor
    }

    /**
     * Copy constructor used for cloning.
     *
     * @param artifact the object to be copied.
     */
    protected GovernanceArtifactImpl(GovernanceArtifactImpl artifact) {
        if (artifact != null) {
            this.attributes = artifact.attributes;
            this.properties = artifact.properties;
            this.lcName = artifact.lcName;
            this.lcState = artifact.lcState;
            this.uniqueAttributes = artifact.uniqueAttributes;
//            if (artifact.checkListItemBeans != null) {
//                this.checkListItemBeans = Arrays.copyOf(artifact.checkListItemBeans, artifact.checkListItemBeans.length);
//            }
//            if (artifact.approveItemBeans != null) {
//                this.approveItemBeans = Arrays.copyOf(artifact.approveItemBeans, artifact.approveItemBeans.length);
//            }
            this.artifactPath = artifact.artifactPath;
            try {
                associateRegistry(artifact.getAssociatedRegistry());
            } catch (GovernanceException ignored) {
            }
            setId(artifact.getId());
        }
    }

    protected GovernanceArtifactImpl(GovernanceArtifactImpl artifact,  List<String> uniqueAttributes) {
       this(artifact);
       setUniqueAttributes(uniqueAttributes);
    }

    /**
     * Constructor accepting resource identifier and the XML content.
     *
     * @param id             the resource identifier.
     * @param contentElement an XML element containing the content.
     * @throws GovernanceException if the construction fails.
     */
    public GovernanceArtifactImpl(String id, OMElement contentElement) throws GovernanceException {
        this(id);
        serializeToAttributes(contentElement, null);
    }

    public GovernanceArtifactImpl(String id, OMElement contentElement, List<String> uniqueAttributes) throws GovernanceException {
        this(id, contentElement);
        setUniqueAttributes(uniqueAttributes);
    }

    // Method to serialize attributes.
    private void serializeToAttributes(OMElement contentElement, String parentAttributeName)
            throws GovernanceException {
        Iterator childIt = contentElement.getChildren();
        if (childIt.hasNext()) {
        	while (childIt.hasNext()) {
                Object childObj = childIt.next();
                if (childObj instanceof OMElement) {
                    OMElement childElement = (OMElement) childObj;
                    String elementName = childElement.getLocalName();
                    String attributeName =
                            (parentAttributeName == null ? "" : parentAttributeName + "_") +
                                    elementName;
                    serializeToAttributes(childElement, attributeName);
                } else if (childObj instanceof OMText) {
                    OMText childText = (OMText) childObj;
                    if (childText.getNextOMSibling() == null &&
                            childText.getPreviousOMSibling() == null) {
                        // if it is only child, we consider it is a value.
                        String textValue = childText.getText();
                        addAttribute(parentAttributeName, textValue);
                    }
                }
            }
        } else {
        	if(!contentElement.getChildElements().hasNext()){
        		addAttribute(parentAttributeName, null);
        	}
        }
        
    }

    public static GovernanceArtifactImpl create(final Registry registry, final String artifactId)
            throws GovernanceException {
        return new GovernanceArtifactImpl(artifactId) {
            {
                associateRegistry(registry);
            }

            public QName getQName() {
                return null;
            }
            public void setQName(QName qName) throws GovernanceException {

            }
        };
    }

    public static GovernanceArtifactImpl create(final Registry registry, final String artifactId,
                                                final OMElement content) throws GovernanceException {
        return new GovernanceArtifactImpl(artifactId, content) {
            {
                associateRegistry(registry);
            }

            public QName getQName() {
                return null;
            }
            public void setQName (QName qName)  throws GovernanceException {}
        };
    }

    public static GovernanceArtifactImpl create(final Registry registry, final String artifactId,
                                                final List<String> uniqueAttributes)
                                                throws GovernanceException {
        GovernanceArtifactImpl artifact = create(registry, artifactId);
        artifact.setUniqueAttributes(uniqueAttributes);
        return artifact;
    }

    public static GovernanceArtifactImpl create(final Registry registry, final String artifactId,
                                                final OMElement content, List<String> uniqueAttributes)
                                                throws GovernanceException {
        GovernanceArtifactImpl artifact = create(registry, artifactId, content);
        artifact.setUniqueAttributes(uniqueAttributes);
        return artifact;
    }

    /**
     * Returns the id of the artifact
     *
     * @return the id
     */
    @Override
    public String getId() {
        return id;
    }

    /**
     * Set the id
     *
     * @param id the id
     */
    @Override
    public void setId(String id) {
        this.id = id;
    }


    /**
     * Returns the path of the artifact, need to save the artifact before
     * getting the path.
     *
     * @return here we return the path of the artifact.
     * @throws GovernanceException if an error occurred.
     */
    @Override
    public String getPath() throws GovernanceException {
        if (path == null) {
            path = GovernanceUtils.getArtifactPath(registry, id);
        }
        return path;
    }

    public String getMediaType() {
        if(path == null) {
            try {
                path = getPath();
            } catch (GovernanceException ex) {
                log.error("An error occurred while obtaining the path of artifact " + ex);
                return null;
            }
        }
        if(path != null) {
            try {
                if (registry.resourceExists(path)) {
                    return registry.get(path).getMediaType();
                }
            } catch(RegistryException ex) {
                log.error("An error occurred obtaining the media type of the artifact " + ex);
            }
        }
        return null;
    }

    /**
     * Returns the name of the lifecycle associated with this artifact.
     *
     * @return the name of the lifecycle associated with this artifact.
     * @throws GovernanceException if an error occurred.
     */
    @Override
    public String getLifecycleName() throws GovernanceException {
        String path = getPath();
        if (path != null) {
            try {
                if (!registry.resourceExists(path)) {
                    String msg =
                            "The artifact is not added to the registry. Please add the artifact " +
                                    "before reading lifecycle information.";
                    log.error(msg);
                    throw new GovernanceException(msg);
                }
                return registry.get(path).getProperty("registry.LC.name");
            } catch (RegistryException e) {
                String msg = "Error in obtaining lifecycle name for the artifact. id: " + id +
                        ", path: " + path + ".";
                log.error(msg, e);
                throw new GovernanceException(msg, e);
            }
        }
        return null;
    }

    /**
     * Returns the name of the lifecycle associated with this artifact.
     *
     * @return the name of the lifecycle associated with this artifact.
     * @throws GovernanceException if an error occurred.
     */
    @Override
    public String[] getLifecycleNames() throws GovernanceException {
        String path = getPath();
        if (path != null) {
            try {
                if (!registry.resourceExists(path)) {
                    String msg =
                            "The artifact is not added to the registry. Please add the artifact " +
                                    "before reading lifecycle information.";
                    log.error(msg);
                    throw new GovernanceException(msg);
                }

                List<String> lifeCycleNames = new ArrayList<String>();
                Resource resource = registry.get(path);
                for (Object object : resource.getProperties().keySet()) {
                    String property = (String) object;
                    if (property.startsWith("registry.LC.name.")) {
                        lifeCycleNames.add(resource.getProperty(property));
                    }
                }

                return lifeCycleNames.toArray(new String[lifeCycleNames.size()]);
            } catch (RegistryException e) {
                String msg = "Error in obtaining lifecycle names for the artifact. id: " + id +
                        ", path: " + path + ".";
                log.error(msg, e);
                throw new GovernanceException(msg, e);
            }
        }
        return null;
    }

    /**
     * Associates the named lifecycle with the artifact
     *
     * @param name the name of the lifecycle to be associated with this artifact.
     * @throws GovernanceException if an error occurred.
     */
    @Override
    public void attachLifecycle(String name) throws GovernanceException {
        try {
            String path = getPath();
            if(name != null && path != null) {
                registry.associateAspect(path, name);

                Resource resource = registry.get(path);
                if(resource.getAspects().size() == 1) {
                    // Since this is the first life-cycle we make it default
                    resource.setProperty("registry.LC.name", name);
                    registry.put(path, resource);
                }
            }
        } catch (RegistryException e) {
            String msg = "Error in associating lifecycle for the artifact. id: " + id +
                    ", path: " + path + ".";
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
    }


    /**
     * De-associate lifecycle associated with the artifact
     *
     * @throws GovernanceException if an error occurred.
     */
    public void detachLifecycle(String lifecycleName) throws GovernanceException {
        try {
            GovernanceUtils.removeAspect(path, lifecycleName, registry);
        } catch (RegistryException e) {
            String msg = "Error in de-associating lifecycle for the artifact. id: " + id +
                    ", path: " + path + ".";
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
    }

    /**
     * Returns the state of the lifecycle associated with this artifact.
     *
     * @return the state of the lifecycle associated with this artifact.
     * @throws GovernanceException if an error occurred.
     */
    @Override
    public String getLifecycleState() throws GovernanceException {
        String path = getPath();
        if (path != null) {
            try {
                if (!registry.resourceExists(path)) {
                    String msg =
                            "The artifact is not added to the registry. Please add the artifact " +
                                    "before reading lifecycle information.";
                    log.error(msg);
                    throw new GovernanceException(msg);
                }
                Resource resource = registry.get(path);
                for (Object object : resource.getProperties().keySet()) {
                    String property = (String) object;
                    if (property.startsWith("registry.lifecycle.") && property.endsWith(".state") && getLifecycleName() != null && property.contains((getLifecycleName()))) {
                        lcState = resource.getProperty(property);
                        return lcState;
                    }
                }
            } catch (RegistryException e) {
                String msg = "Error in obtaining lifecycle state for the artifact. id: " + id +
                        ", path: " + path + ".";
                log.error(msg, e);
                throw new GovernanceException(msg, e);
            }
        }
        return null;
    }

    /**
     * Returns the state of the lifecycle associated with this artifact.
     *
     * @return the state of the lifecycle associated with this artifact.
     * @throws GovernanceException if an error occurred.
     */
    @Override
    public String getLifecycleState(String lifeCycleName) throws GovernanceException {
        String path = getPath();
        if (path != null) {
            try {
                if (!registry.resourceExists(path)) {
                    String msg =
                            "The artifact is not added to the registry. Please add the artifact " +
                                    "before reading lifecycle information.";
                    log.error(msg);
                    throw new GovernanceException(msg);
                }
                Resource resource = registry.get(path);
                for (Object object : resource.getProperties().keySet()) {
                    String property = (String) object;
                    if (property.startsWith("registry.lifecycle.") && property.endsWith(".state") &&
                            property.substring(0,property.lastIndexOf(".")).endsWith(lifeCycleName)) {
                        return resource.getProperty(property);
                    }
                }
            } catch (RegistryException e) {
                String msg = "Error in obtaining lifecycle state for the artifact. id: " + id +
                        ", path: " + path + ".";
                log.error(msg, e);
                throw new GovernanceException(msg, e);
            }
        }
        return null;
    }

    /**
     * update the path after moving the resource.
     *
     * @throws GovernanceException if an error occurred.
     */
    public void updatePath() throws GovernanceException {
        path = GovernanceUtils.getArtifactPath(registry, id);
    }

    /**
     * update the path after moving the resource.
     *
     * @param artifactId id of the artifact
     * @throws GovernanceException if an error occurred.
     */
    public void updatePath(String artifactId) throws GovernanceException {
        path = GovernanceUtils.getArtifactPath(registry, artifactId);
    }

    /**
     * Create a version of the artifact.
     *
     * @throws GovernanceException throws if the operation failed.
     */
    public void createVersion() throws GovernanceException {
        checkRegistryResourceAssociation();
        try {
            if (!registry.resourceExists(path)) {
                String msg =
                        "The artifact is not added to the registry. Please add the artifact " +
                                "before creating versions.";
                log.error(msg);
                throw new GovernanceException(msg);
            }
            registry.createVersion(path);
        } catch (RegistryException e) {
            String msg = "Error in creating a version for the artifact. id: " + id +
                    ", path: " + path + ".";
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
    }

    /**
     * Associate a registry, this is mostly used by the artifact manager when creating the
     * artifact.
     *
     * @param registry the registry.
     * @throws GovernanceException throws if the operation failed.
     */
    public void associateRegistry(Registry registry) throws GovernanceException {
        this.registry = registry;
    }

    /**
     * Adding an attribute to the artifact. The artifact should be saved to get effect the change.
     * In the case of a single-valued attribute, this method will set or replace the existing
     * attribute with the provided value. In the case of a multi-valued attribute, this method will
     * append the provided value to the existing list.
     *
     * @param key   the key.
     * @param value the value.
     * @throws GovernanceException throws if the operation failed.
     */
    @Override
    public void addAttribute(String key, String value) throws GovernanceException {
        boolean isAttribute = key.contains(".");
        if (!isAttribute) {
            List<String> values = attributes.get(key);
            if (values == null) {
                values = new ArrayList<String>();
                attributes.put(key, values);
            }
            values.add(value);
        } else {
            List<String> values = properties.get(key);
            if (values == null) {
                values = new ArrayList<String>();
                properties.put(key, values);
            }
            values.add(value);
        }
    }

    /**
     * Set/Update an attribute with multiple values. The artifact should be saved to get effect the
     * change.
     *
     * @param key       the key
     * @param newValues the value
     * @throws GovernanceException throws if the operation failed.
     */
    @Override
    public void setAttributes(String key, String[] newValues) throws GovernanceException {
        boolean isAttribute = key.contains(".");
        if (!isAttribute) {
            List<String> values = new ArrayList<String>();
            values.addAll(Arrays.asList(newValues));
            attributes.put(key, values);
        } else {
            List<String> values = properties.get(key);
            if (values == null) {
                values = new ArrayList<String>();
                properties.put(key, values);
            }
            values.addAll(Arrays.asList(newValues));
        }

    }

    /**
     * Set/Update an attribute with a single value. The artifact should be saved to get effect the
     * change. This method will replace the existing attribute with the provided value. In the case
     * of a multi-valued attribute this will remove all existing values. If you want to append the
     * provided value to a list values of a multi-valued attribute, use the addAttribute method
     * instead.
     *
     * @param key      the key
     * @param newValue the value
     * @throws GovernanceException throws if the operation failed.
     */
    @Override
    public void setAttribute(String key, String newValue) throws GovernanceException {
        boolean isAttribute = key.contains(".");
        if (!isAttribute) {
            List<String> values = new ArrayList<String>();
            values.add(newValue);
            attributes.put(key, values);
        } else {
            List<String> values = properties.get(key);
            if (values == null) {
                values = new ArrayList<String>();
                properties.put(key, values);
            }
            values.add(newValue);
        }

    }

    /**
     * Returns the attribute of a given key.
     *
     * @param key the key
     * @return the value of the attribute, if there are more than one attribute for the key this
     *         returns the first value.
     * @throws GovernanceException throws if the operation failed.
     */
    @Override
    public String getAttribute(String key) throws GovernanceException {
        boolean isAttribute = key.contains(".");
        if (!isAttribute) {
            List<String> values = attributes.get(key);
            if (values == null || values.size() == 0) {
                return null;
            }
            return values.get(0);
        } else {
            List<String> values = properties.get(key);
            if (values == null || values.size() == 0) {
                return null;
            }
            return values.get(0);
        }

    }

    /**
     * Returns the available attribute keys
     *
     * @return an array of attribute keys.
     * @throws GovernanceException throws if the operation failed.
     */
    @Override
    public String[] getAttributeKeys() throws GovernanceException {
        Set<String> attributeKeys = attributes.keySet();
        return attributeKeys.toArray(new String[attributeKeys.size()]);
    }

    /**
     * Returns the attribute values for a key.
     *
     * @param key the key.
     * @return attribute values for the key.
     * @throws GovernanceException throws if the operation failed.
     */
    @Override
    public String[] getAttributes(String key) throws GovernanceException {
        boolean isAttribute = key.contains(".");
        if (!isAttribute) {
            List<String> values = attributes.get(key);
            if (values == null) {
                return null; //TODO: This should return String[0]
            }
            return values.toArray(new String[values.size()]);
        } else {
            List<String> values = properties.get(key);
            if (values == null) {
                return null; //TODO: This should return String[0]
            }
            return values.toArray(new String[values.size()]);
        }
    }

    /**
     * Remove attribute with the given key. The artifact should be saved to get effect the change.
     *
     * @param key the key
     * @throws GovernanceException throws if the operation failed.
     */
    @Override
    public void removeAttribute(String key) throws GovernanceException {
        boolean isAttribute = key.contains(".");
        if (!isAttribute) {
            attributes.remove(key);
        } else {
            properties.remove(key);
        }

    }

    /**
     * Get dependencies of an artifacts. The artifacts should be saved, before calling this method.
     *
     * @return an array of dependencies of this artifact.
     * @throws GovernanceException throws if the operation failed.
     */
    @Override
    public GovernanceArtifact[] getDependencies() throws GovernanceException {
        checkRegistryResourceAssociation();
        // uses the path from the getter to make sure the used overloaded method
        String path = getPath();
        List<GovernanceArtifact> governanceArtifacts = new ArrayList<GovernanceArtifact>();
        try {
            Association[] associations =
                    registry.getAssociations(path, GovernanceConstants.DEPENDS);
            for (Association association : associations) {
                String destinationPath = association.getDestinationPath();
                if (!destinationPath.equals(path)) {
                    GovernanceArtifact governanceArtifact =
                            GovernanceUtils.retrieveGovernanceArtifactByPath(registry, destinationPath);
                    //The Governance Artifact may not be returned if the user does not have permission to access it
                    if(governanceArtifact!=null){
                        governanceArtifacts.add(governanceArtifact);
                    }
                }
            }
        } catch (RegistryException e) {
            String msg = "Error in getting dependencies from the artifact. id: " + id +
                    ", path: " + path + ".";
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
        return governanceArtifacts.toArray(new GovernanceArtifact[governanceArtifacts.size()]);
    }

    /**
     * Get dependents of an artifact. The artifacts should be saved, before calling this method.
     *
     * @return an array of artifacts that is dependent on this artifact.
     * @throws GovernanceException throws if the operation failed.
     */
    @Override
    public GovernanceArtifact[] getDependents() throws GovernanceException {
        checkRegistryResourceAssociation();
        // uses the path from the getter to make sure the used overloaded method
        String path = getPath();
        List<GovernanceArtifact> governanceArtifacts = new ArrayList<GovernanceArtifact>();
        try {
            Association[] associations =
                    registry.getAssociations(path, GovernanceConstants.USED_BY);
            for (Association association : associations) {
                String destinationPath = association.getDestinationPath();
                if (!destinationPath.equals(path)) {
                    GovernanceArtifact governanceArtifact =
                            GovernanceUtils.retrieveGovernanceArtifactByPath(registry, destinationPath);
                    //The Governance Artifact may not be returned if the user does not have permission to access it
                    if(governanceArtifact!=null){
                        governanceArtifacts.add(governanceArtifact);
                    }
                }
            }
        } catch (RegistryException e) {
            String msg = "Error in getting dependents from the artifact. id: " + id +
                    ", path: " + path + ".";
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
        return governanceArtifacts.toArray(new GovernanceArtifact[governanceArtifacts.size()]);
    }

    /**
     * Get all lifecycle actions for the current state of the lifecycle
     *
     * @param lifeCycleName lifecycle name of which actions are needed
     * @return Action set which can be invoked
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException
     *          throws if the operation failed.
     */
    public String[] getAllLifecycleActions(String lifeCycleName) throws GovernanceException {
    	String path = getPath();
        try {
            return registry.getAspectActions(path, lifeCycleName);
        } catch (RegistryException e) {
            String lifecycleState = getLifecycleState(lifeCycleName);
            String msg = "Error while retrieving the lifecycle actions " +
                    "for lifecycle: " + lifeCycleName + " in lifecycle state: " + lifecycleState;
            throw new GovernanceException(msg, e);
        }
    }

    /**
     * Invoke lifecycle action
     *
     * @param action lifecycle action tobe invoked
     * @param aspectName aspect name of which the action need to be invoked
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException
     *          throws if the operation failed.
     */
    public void invokeAction(String action, String aspectName) throws GovernanceException {
        invokeAction(action, new HashMap<String, String>(), aspectName);
    }

    /**
     * Invoke lifecycle action
     *
     * @param action     lifecycle action tobe invoked
     * @param parameters extra parameters needed when promoting
     * @param aspectName aspect name of which the action need to be invoked
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException
     *          throws if the operation failed.
     */
    public void invokeAction(String action, Map<String, String> parameters, String aspectName) throws GovernanceException {
        Resource artifactResource = getArtifactResource();
        CheckListItemBean[] checkListItemBeans = GovernanceUtils.getAllCheckListItemBeans(artifactResource, this, aspectName);
        try {
            if (checkListItemBeans != null) {
                for (CheckListItemBean checkListItemBean : checkListItemBeans) {
                    parameters.put(checkListItemBean.getOrder() + ".item", checkListItemBean.getValue().toString());
                }
            }
            registry.invokeAspect(getArtifactPath(), aspectName, action, parameters);
        } catch (RegistryException e) {
            String msg = "Invoking lifecycle action \"" + action + "\" failed. " + e.getMessage();
            log.error(msg, e);
            throw new GovernanceException(e.getMessage(), e);
        }
    }

    /**
     * Retrieve name set of the checklist items
     *
     * @param aspectName lifecycle name of which action to be invoked
     * @return Checklist item name set
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException
     *          throws if the operation failed.
     */
    public String[] getAllCheckListItemNames(String aspectName) throws GovernanceException {
        Resource artifactResource = getArtifactResource();
        CheckListItemBean[] checkListItemBeans = GovernanceUtils.getAllCheckListItemBeans(artifactResource, this, aspectName);
        if (checkListItemBeans == null) {
            return null;
        }
        String[] checkListItemNames = new String[checkListItemBeans.length];
        for (CheckListItemBean checkListItemBean : checkListItemBeans) {
            checkListItemNames[checkListItemBean.getOrder()] = checkListItemBean.getName();
        }
        return checkListItemNames;
    }

    /**
     * Check the checklist item
     *
     * @param aspectName lifecycle name of which action to be invoked
     * @param order order of the checklist item need to checked
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException
     *          throws if the operation failed.
     */
    public void checkLCItem(int order, String aspectName) throws GovernanceException {
        Resource artifactResource = getArtifactResource();
        CheckListItemBean[] checkListItemBeans = GovernanceUtils.getAllCheckListItemBeans(artifactResource, this, aspectName);
        if (checkListItemBeans == null || order < 0 || order >= checkListItemBeans.length) {
            throw new GovernanceException("Invalid check list item.");
        } else if (checkListItemBeans[order].getValue()) {
            throw new GovernanceException("lifecycle checklist item \"" +
                    checkListItemBeans[order].getName() + "\" already checked");
        }
        try {
            setCheckListItemValue(order, true, checkListItemBeans, aspectName);
        } catch (RegistryException e) {
            String msg = "Checking LC item failed for check list item " + checkListItemBeans[order].getName();
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
    }

    /**
     * Check whether the given ordered lifecycle checklist item is checked or not
     *
     * @param order order of the checklist item need to unchecked
     * @param aspectName lifecycle name of which action to be invoked
     * @return whether the given ordered lifecycle checklist item is checked or not
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException
     *          throws if the operation failed.
     */
    public boolean isLCItemChecked(int order, String aspectName) throws GovernanceException {
        Resource artifactResource = getArtifactResource();
        CheckListItemBean[] checkListItemBeans = GovernanceUtils.getAllCheckListItemBeans(artifactResource, this, aspectName);
        if (checkListItemBeans == null || order < 0 || order >= checkListItemBeans.length) {
            throw new GovernanceException("Invalid check list item.");
        }
        return checkListItemBeans[order].getValue();

    }

    /**
     * Un-check the checklist item
     *
     * @param order order of the checklist item need to unchecked
     * @param aspectName lifecycle name of which action to be invoked
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException
     *          throws if the operation failed.
     */
    public void uncheckLCItem(int order, String aspectName) throws GovernanceException {
        Resource artifactResource = getArtifactResource();
        CheckListItemBean[] checkListItemBeans = GovernanceUtils.getAllCheckListItemBeans(artifactResource, this, aspectName);
        if (checkListItemBeans == null || order < 0 || order >= checkListItemBeans.length) {
            throw new GovernanceException("Invalid check list item.");
        } else if (!checkListItemBeans[order].getValue()) {
            throw new GovernanceException("lifecycle checklist item \"" +
                    checkListItemBeans[order].getName() + "\" not checked");
        }
        try {
            setCheckListItemValue(order, false, checkListItemBeans, aspectName);
        } catch (RegistryException e) {
            String msg = "Unchecking LC item failed for check list item: " + checkListItemBeans[order].getName();
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
    }

    /**
     * Set the checklist item value
     *
     * @param order order of the checklist item
     * @param value value of the checklist item
     * @param aspectName aspect name
     * @throws RegistryException throws if the operation failed.
     */
    private void setCheckListItemValue(int order, boolean value,
                                       CheckListItemBean[] checkListItemBeans, String aspectName) throws RegistryException {
        checkListItemBeans[order].setValue(value);
        Map<String, String> parameters = new HashMap<String, String>();
        for (CheckListItemBean checkListItemBean : checkListItemBeans) {
            parameters.put(checkListItemBean.getOrder() + ".item", checkListItemBean.getValue().toString());
        }
        registry.invokeAspect(getArtifactPath(), aspectName, "itemClick", parameters);
    }

    /**
     * Retrieve action set which need votes.
     *
     * @param aspectName Lifecycle name
     * @return Action set which can vote
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException throws if the operation failed.
     */
    public String[] getAllVotingItems(String aspectName) throws GovernanceException {
        Resource artifactResource = getArtifactResource();
        ApproveItemBean[] approveItemBeans = GovernanceUtils.
                getAllApproveItemBeans(((UserRegistry) registry).getUserName(), artifactResource, this, aspectName);
        if (approveItemBeans == null) {
            throw new GovernanceException("No voting event found for the lifecycle: " + getLcName() +
                                                  " in lifecycle state: " + getLcState() + " of the artifact " +
                                                  getQName().getLocalPart());
        }
        String[] votingItems = new String[approveItemBeans.length];
        for (ApproveItemBean approveItemBean : approveItemBeans) {
            votingItems[approveItemBean.getOrder()] = approveItemBean.getName();
        }
        return votingItems;
    }

    /**
     * Retrieve action set which need votes.
     *
     * @return Action set which can vote
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException throws if the operation failed.
     */
    public String[] getAllVotingItems() throws GovernanceException {
        return getAllVotingItems(getLifecycleName());
    }

    /**
     * Vote for an action.
     *
     * @param order      order of the action which need to be voted
     * @param aspectName Lifecycle name
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException throws if the operation failed.
     */
    public void vote(int order, String aspectName) throws GovernanceException {
        Resource artifactResource = getArtifactResource();
        ApproveItemBean[] approveItemBeans = GovernanceUtils.
                getAllApproveItemBeans(((UserRegistry) registry).getUserName(), artifactResource, this, aspectName);
        if (approveItemBeans == null || order < 0 || order >= approveItemBeans.length) {
            throw new GovernanceException("Invalid voting action selected");
        } else if (approveItemBeans[order].getValue()) {
            throw new GovernanceException("Already voted for the action " + approveItemBeans[order].getName());
        }
        try {
            setVotingItemValue(order, true, approveItemBeans, aspectName);
        } catch (RegistryException e) {
            String msg = "Voting failed for action " + approveItemBeans[order].getName();
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
    }

    /**
     * Vote for an action.
     *
     * @param order order of the action which need to be voted
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException throws if the operation failed.
     */
    public void vote(int order) throws GovernanceException {
        vote(order, getLifecycleName());
    }

    /**
     * Check whether the current user voted for given order event
     *
     * @param order      order of the action which need to be voted
     * @param aspectName Lifecycle name
     * @return whether the current user voted for the given order event
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException throws if the operation failed.
     */
    public boolean isVoted(int order, String aspectName) throws GovernanceException {
        Resource artifactResource = getArtifactResource();
        ApproveItemBean[] approveItemBeans = GovernanceUtils.
                getAllApproveItemBeans(((UserRegistry) registry).getUserName(), artifactResource, this, aspectName);
        if (approveItemBeans == null || order < 0 || order >= approveItemBeans.length) {
            throw new GovernanceException("Invalid voting action selected");
        }
        return approveItemBeans[order].getValue();
    }

    /**
     * Check whether the current user voted for given order event.
     *
     * @param order order of the action which need to be voted
     * @return whether the current user voted for the given order event
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException throws if the operation failed.
     */
    public boolean isVoted(int order) throws GovernanceException {
        return isVoted(order, getLifecycleName());
    }

    /**
     * Unvote for an action
     *
     * @param order      order of the action which need to be unvoted
     * @param aspectName Lifecycle name
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException throws if the operation failed.
     */
    public void unvote(int order, String aspectName) throws GovernanceException {
        Resource artifactResource = getArtifactResource();
        ApproveItemBean[] approveItemBeans = GovernanceUtils.
                getAllApproveItemBeans(((UserRegistry) registry).getUserName(), artifactResource, this, aspectName);
        if (approveItemBeans == null || order < 0 || order >= approveItemBeans.length) {
            throw new GovernanceException("Invalid voting action selected");
        } else if (!approveItemBeans[order].getValue()) {
            throw new GovernanceException("Not voted for the action \""
                                                  + approveItemBeans[order].getName() + "\"");
        }
        try {
            setVotingItemValue(order, false, approveItemBeans, aspectName);
        } catch (RegistryException e) {
            String msg = "Unvoting failed for action \"" + approveItemBeans[order].getName() + "\"";
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
    }

    /**
     * Unvote for an action.
     *
     * @param order order of the action which need to be un voted
     * @throws org.wso2.carbon.governance.api.exception.GovernanceException throws if the operation failed.
     */
    public void unvote(int order) throws GovernanceException {
        unvote(order, getLifecycleName());
    }

    /**
     * Set the approval value
     *
     * @param order order of the approve event
     * @param value value of the approve
     * @throws RegistryException throws if the operation failed.
     */
    private void setVotingItemValue(int order, boolean value,
                                    ApproveItemBean[] approveItemBeans, String aspectName) throws RegistryException {
        approveItemBeans[order].setValue(value);
        Map<String, String> parameters = new HashMap<String, String>();
        for (ApproveItemBean approveItemBean : approveItemBeans) {
            parameters.put(approveItemBean.getOrder() + ".vote", approveItemBean.getValue().toString());
        }
        registry.invokeAspect(getArtifactPath(), aspectName, "voteClick", parameters);
    }

    /**
     * Attach the current artifact to an another artifact. Both the artifacts should be saved,
     * before calling this method. This method will two generic artifact types. There are specific
     * methods
     *
     * @param attachedToArtifact the artifact the current artifact is attached to
     * @throws GovernanceException throws if the operation failed.
     */
    public void attach(GovernanceArtifact attachedToArtifact) throws GovernanceException {
        checkRegistryResourceAssociation();
        // uses the path from the getter to make sure the used overloaded method
        String path = getPath();
        String attachedToArtifactPath = attachedToArtifact.getPath();
        if (attachedToArtifactPath == null) {
            String msg = "'Attached to artifact' is not associated with a registry path.";
            log.error(msg);
            throw new GovernanceException(msg);
        }
        try {
            registry.addAssociation(attachedToArtifactPath, path, GovernanceConstants.USED_BY);
            registry.addAssociation(path, attachedToArtifactPath, GovernanceConstants.DEPENDS);
        } catch (RegistryException e) {
            String msg = "Error in attaching the artifact. source id: " + id + ", path: " + path +
                    ", target id: " + attachedToArtifact.getId() + ", path:" +
                    attachedToArtifactPath +
                    ", attachment type: " + attachedToArtifact.getClass().getName() + ".";
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
    }


    /**
     * Detach the current artifact from the provided artifact. Both the artifacts should be saved,
     * before calling this method.
     *
     * @param artifactId the artifact id of the attached artifact
     * @throws GovernanceException throws if the operation failed.
     */
    public void detach(String artifactId) throws GovernanceException {
        checkRegistryResourceAssociation();
        // uses the path from the getter to make sure the used overloaded method
        String path = getPath();
        String artifactPath = GovernanceUtils.getArtifactPath(registry, artifactId);
        if (artifactPath == null) {
            String msg = "Attached to artifact is not associated with a registry path.";
            log.error(msg);
            throw new GovernanceException(msg);
        }
        try {
            registry.removeAssociation(path, artifactPath, GovernanceConstants.DEPENDS);
            registry.removeAssociation(artifactPath, path, GovernanceConstants.USED_BY);
        } catch (RegistryException e) {
            String msg = "Error in detaching the artifact. source id: " + id + ", path: " + path +
                    ", target id: " + artifactId +
                    ", target path:" + artifactPath + ".";
            log.error(msg, e);
            throw new GovernanceException(msg, e);
        }
    }

    /**
     * Validate the resource is associated with a registry
     *
     * @throws GovernanceException if the resource is not associated with a registry.
     */
    protected void checkRegistryResourceAssociation() throws GovernanceException {
        // uses the path from the getter to make sure the used overloaded method
        String path = getPath();
        if (registry == null) {
            String msg = "A registry is not associated with the artifact.";
            log.error(msg);
            throw new GovernanceException(msg);
        }
        if (path == null) {
            String msg = "A path is not associated with the artifact.";
            log.error(msg);
            throw new GovernanceException(msg);
        }
        if (id == null) {
            String msg = "An id is not associated with the artifact.";
            log.error(msg);
            throw new GovernanceException(msg);
        }
    }

    /**
     * Returns the associated registry to the artifact.
     *
     * @return the associated registry
     */
    protected Registry getAssociatedRegistry() {
        return registry;
    }

    /**
     * Get the resource related to this artifact
     *
     * @return resource related to this artifact
     * @throws GovernanceException if there is no resource related to the artifact in the registry
     */
    private Resource getArtifactResource() throws GovernanceException {
        Resource artifactResource;
        try {
            return registry.get(artifactPath);
        } catch (RegistryException e) {
            String msg = "Artifact resource \"" + getQName().getLocalPart() + "\" not found in the registry";
            throw new GovernanceException();
        }
    }

    /**
     * Returns the available attribute keys
     *
     * @return an array of attribute keys.
     * @throws GovernanceException throws if the operation failed.
     */
    @Override
    public String[] getPropertyKeys() throws GovernanceException {
        Set<String> attributeKeys = properties.keySet();
        return attributeKeys.toArray(new String[attributeKeys.size()]);
    }

    @Override
    public boolean equals(Object artifact) {
        GovernanceArtifact governanceArtifact ;
        if(!(artifact instanceof GovernanceArtifact)) {
            return false;
        } else {
            governanceArtifact = (GovernanceArtifact) artifact;
        }

        return governanceArtifact.getId().equals(this.getId());
    }

    @Override
    public void attach(String artifactId) throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public void addAssociation(String associationType, GovernanceArtifact attachedToArtifact)
            throws GovernanceException {
        checkRegistryResourceAssociation();
        // uses the path from the getter to make sure the used overloaded method
        String path = getPath();
        String attachedToArtifactPath = attachedToArtifact.getPath();
        if (attachedToArtifactPath == null) {
            String msg = "'Attached to artifact' is not associated with a registry path.";
            log.error(msg);
            throw new GovernanceException(msg);
        }
        try {
            registry.addAssociation(path, attachedToArtifactPath, associationType);
        } catch (RegistryException e) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("Error in attaching the artifact. source id: ")
                    .append(id)
                    .append(", path: ")
                    .append(path)
                    .append(", target id: ")
                    .append(attachedToArtifact.getId())
                    .append(", path:")
                    .append(attachedToArtifactPath)
                    .append(", attachment type: ")
                    .append(attachedToArtifact.getClass().getName());
            throw new GovernanceException(stringBuilder.toString(), e);
        }    }

    @Override
    public void addAssociation(String associationType, String artifactId) throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public void addBidirectionalAssociation(String forwardType, String backwardType,
                                            GovernanceArtifact attachedToArtifact) throws GovernanceException {
        checkRegistryResourceAssociation();
        // uses the path from the getter to make sure the used overloaded method
        String path = getPath();
        String attachedToArtifactPath = attachedToArtifact.getPath();
        if (attachedToArtifactPath == null) {
            String msg = "'Attached to artifact' is not associated with a registry path.";
            log.error(msg);
            throw new GovernanceException(msg);
        }
        try {
            registry.addAssociation(path, attachedToArtifactPath, forwardType);
            registry.addAssociation(attachedToArtifactPath, path, backwardType);
        } catch (RegistryException e) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("Error in attaching the artifact. source id: ")
                    .append(id)
                    .append(", path: ")
                    .append(path)
                    .append(", target id: ")
                    .append(attachedToArtifact.getId())
                    .append(", path:")
                    .append(attachedToArtifactPath)
                    .append(", attachment type: ")
                    .append(attachedToArtifact.getClass().getName());
            throw new GovernanceException(stringBuilder.toString(), e);
        }
    }

    @Override
    public void removeAssociation(String associationType, String artifactId) throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public void removeAssociation(String artifactId) throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public Map<String, List<GovernanceArtifact>> getAssociations() throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public Map<String, List<String>> getAssociatedArtifactIds() throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public boolean isRegistryAwareArtifact() {
        try {
            checkRegistryResourceAssociation();
            return true;
        } catch (GovernanceException e) {
            return false;
        }
    }

    @Override
    public void addTag(String tag) throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public void addTags(List<String> tags) throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public List<String> listTags() throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public void removeTag(String tag) throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public void removeTags(List<String> tags) throws GovernanceException {
        throw new UnsupportedOperationException("Not yet Implemented");
    }

    @Override
    public String toString() {
        return "GovernanceArtifactImpl{" +
               "attributes=" + attributes +
               ", id='" + id + '\'' +
               '}';
    }

    public boolean uniqueTo(GovernanceArtifact artifact) {
        if (artifact != null) {
            List<String> uAttributes = getUniqueAttributes(artifact);
            if (this == artifact || this.equals(artifact)) {
                return true;
            } else if (uAttributes != null && uAttributes.size() > 0) {
                try {
                    for (String attributeName : uAttributes) {
                        if (this.getAttribute(attributeName) != null && artifact.getAttribute(attributeName) !=
                                                                        null && !this.getAttribute(attributeName).equals
                                (artifact.getAttribute(attributeName))) {
                            return false;
                        }
                    }
                    //all unique attributes are same
                    return true;
                } catch (GovernanceException e) {
                    log.error(e);
                }
            }
        }
        return false;
    }

    private List<String> getUniqueAttributes(GovernanceArtifact artifact) {
        if(this.uniqueAttributes != null){
            return uniqueAttributes;
        } else if(artifact instanceof GovernanceArtifactImpl){
            return ((GovernanceArtifactImpl)artifact).getUniqueAttributes();
        }
        return null;
    }

    public boolean compareTo(GovernanceArtifact artifact) {
        if (artifact != null) {
            if (this == artifact || this.equals(artifact)) {
                return true;
            } else {
                try {
                    for (String key : this.getAttributeKeys()) {
                        if (!this.getAttribute(key).equals(artifact.getAttribute(key))) {
                            return false;
                        }
                    }
                    //all unique attributes are same
                    return true;
                } catch (GovernanceException e) {
                    log.error(e);
                }
            }
        }
        return false;
    }

    /**
     * TODO :Move this implementation to a solr based implementation in C5.
     */
    /**
     * This method is used to get a lifecycle's current state duration information.
     *
     * @param artifactID lifecycle associated artifacts id.
     * @param lcName     lifecycle name.
     * @return a map of current lifecycle state duration colour and duration.
     * @throws GovernanceException
     */
    @Override
    public Map<String, String> getCurrentStateDuration(String artifactID, String lcName) throws GovernanceException {

        if (artifactPath == null || lcName == null) {
            return null;
        }

        String durationColour = null;
        String currentStateDuration = null;
        String lifecycleName;
        Map<String, String> currentLCStateDurationInfo = new HashMap<>();

        if (lcName.isEmpty()) {
            lcName = getLifecycleName();
        }
        String artifactPath = GovernanceUtils.getArtifactPath(registry, artifactID);
        try {
            if (registry.resourceExists(artifactPath)) {
                Resource resource = registry.get(artifactPath);
                List<String> checkpoints = resource.getPropertyValues(GovernanceConstants.REGISTRY_LIFECYCLE + lcName
                        + GovernanceConstants.CHECKPOINT);

                if (checkpoints != null) {

                    for (String checkpoint : checkpoints) {
                        List<String> checkpointValues = resource
                                .getPropertyValues(GovernanceConstants.REGISTRY_LIFECYCLE + lcName +
                                        GovernanceConstants.CHECKPOINT + GovernanceConstants.DOT + checkpoint);
                        if (checkpointValues != null) {
                            String checkpointName = checkpointValues.get(0);
                            String checkpointMin = checkpointValues.get(1);
                            String checkpointMax = checkpointValues.get(2);
                            String checkpointLastUpdatedTime = checkpointValues.get(3);
                            String checkpointColour = checkpointValues.get(4);

                            long duration = CheckpointTimeUtils.calculateTimeDifference(CheckpointTimeUtils
                                    .getCurrentTime(), checkpointLastUpdatedTime);
                            if (CheckpointTimeUtils.isDurationBetweenTimestamps(duration, checkpointMin,
                                    checkpointMax)) {
                                currentLCStateDurationInfo
                                        .put(GovernanceConstants.LIFECYCLE_DURATION_COLOUR, checkpointColour);
                                currentLCStateDurationInfo
                                        .put(GovernanceConstants.LIFECYCLE_DURATION, CheckpointTimeUtils
                                                .formatTimeDuration(duration));
                                break;
                            }
                        }
                    }
                } else {
                    String lastUpdateTIme = resource.getProperty(GovernanceConstants.REGISTRY_LIFECYCLE + lcName +
                            GovernanceConstants.LAST_UPDATED_TIME);
                    if (lastUpdateTIme != null) {
                        long duration = CheckpointTimeUtils.calculateTimeDifferenceToPresent(lastUpdateTIme);
                        currentLCStateDurationInfo.put(GovernanceConstants.LIFECYCLE_DURATION_COLOUR, null);
                        currentLCStateDurationInfo
                                .put(GovernanceConstants.LIFECYCLE_DURATION,
                                        CheckpointTimeUtils.formatTimeDuration(duration));
                    } else {
                        return null;
                    }
                }

                if (currentLCStateDurationInfo.isEmpty()) {
                    String lastUpdateTIme = resource.getProperty(GovernanceConstants.REGISTRY_LIFECYCLE + lcName +
                            GovernanceConstants.LAST_UPDATED_TIME);
                    if (lastUpdateTIme != null) {
                        long duration = CheckpointTimeUtils.calculateTimeDifferenceToPresent(lastUpdateTIme);
                        currentLCStateDurationInfo.put(GovernanceConstants.LIFECYCLE_DURATION_COLOUR, null);
                        currentLCStateDurationInfo
                                .put(GovernanceConstants.LIFECYCLE_DURATION,
                                        CheckpointTimeUtils.formatTimeDuration(duration));
                    } else {
                        return null;
                    }
                }
            }
        } catch (RegistryException e) {
            throw new GovernanceException("Error occurred while getting current lifecycle state duration info. ", e);
        }

        return currentLCStateDurationInfo;
    }

}
