/*
 *  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.registry.extensions.handlers;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;


import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xerces.xni.parser.XMLInputSource;
import org.uddi.api_v3.AuthToken;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.RegistryConstants;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.ResourcePath;
import org.wso2.carbon.registry.core.config.RegistryContext;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.internal.RegistryCoreServiceComponent;
import org.wso2.carbon.registry.core.jdbc.handlers.RequestContext;
import org.wso2.carbon.registry.core.jdbc.utils.Transaction;
import org.wso2.carbon.registry.core.session.CurrentSession;
import org.wso2.carbon.registry.core.session.UserRegistry;
import org.wso2.carbon.registry.core.utils.RegistryUtils;
import org.wso2.carbon.registry.extensions.beans.BusinessServiceInfo;
import org.wso2.carbon.registry.extensions.handlers.utils.*;
import org.wso2.carbon.registry.extensions.utils.CommonConstants;
import org.wso2.carbon.registry.extensions.utils.CommonUtil;
import org.wso2.carbon.registry.extensions.utils.WSDLValidationInfo;
import org.wso2.carbon.registry.uddi.utils.UDDIUtil;
import org.wso2.carbon.user.core.UserRealm;

@SuppressWarnings({"unused", "UnusedAssignment"})
public class ZipWSDLMediaTypeHandler extends WSDLMediaTypeHandler {

//    <handler class="org.wso2.carbon.registry.extensions.handlers.ZipWSDLMediaTypeHandler">
//        <property name="wsdlMediaType">application/wsdl+xml</property>
//        <property name="schemaMediaType">application/xsd+xml</property>
//        <property name="threadPoolSize">50</property>
//        <property name="useOriginalSchema">true</property>
//        <!--property name="disableWSDLValidation">true</property>
//        <property name="disableSchemaValidation">true</property>
//        <property name="wsdlExtension">.wsdl</property>
//        <property name="schemaExtension">.xsd</property>
//        <property name="archiveExtension">.gar</property>
//        <property name="tempFilePrefix">wsdl</property-->
//        <property name="schemaLocationConfiguration" type="xml">
//            <location>/governance/schemas/</location>
//        </property>
//        <property name="wsdlLocationConfiguration" type="xml">
//            <location>/governance/wsdls/</location>
//        </property>
//        <filter class="org.wso2.carbon.registry.core.jdbc.handlers.filters.MediaTypeMatcher">
//            <property name="mediaType">application/vnd.wso2.governance-archive</property>
//        </filter>
//    </handler>

    private String wsdlMediaType = "application/wsdl+xml";

    private String wsdlExtension = ".wsdl";

    private String xsdMediaType = "application/xsd+xml";

    private String xsdExtension = ".xsd";

    private String swaggerMediaType = "application/swagger+json";

    private String swaggerExtension = ".json";

    private String swaggerLocation = "/swagger/";

    private String wadlMediaType = "application/wadl+xml";

    private String wadlExtension = ".wadl";

    private String wadlLocation = "/wadl/";

    private OMElement wadlLocationConfiguration;

    private String archiveExtension = ".gar";

    private String tempFilePrefix = "wsdl";

    private boolean disableWSDLValidation = false;
    private boolean disableSchemaValidation = false;
    private boolean useOriginalSchema = false;
    private boolean createService = true;

    private boolean disableSymlinkCreation = true;
    private static int numberOfRetry = 5;
    private boolean disableWADLValidation = false;

    private static List<String> skipFileExtensions = new ArrayList<String>();
    private String extensionsSeparator = ",";

    public void setNumberOfRetry(String numberOfRetry) {
        ZipWSDLMediaTypeHandler.numberOfRetry = Integer.parseInt(numberOfRetry);
    }

    public boolean isDisableSymlinkCreation() {
        return disableSymlinkCreation;
    }

    public void setDisableSymlinkCreation(String disableSymlinkCreation) {
        this.disableSymlinkCreation = Boolean.toString(true).equals(disableSymlinkCreation);
    }


    private int threadPoolSize = 50;

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

    public void setThreadPoolSize(String threadPoolSize) {
        this.threadPoolSize = Integer.parseInt(threadPoolSize);
    }

    public OMElement getWADLLocationConfiguration() {
        return wadlLocationConfiguration;
    }

    public void setWadlLocationConfiguration(OMElement locationConfiguration) throws RegistryException {
        Iterator confElements = locationConfiguration.getChildElements();
        while (confElements.hasNext()) {
            OMElement confElement = (OMElement)confElements.next();
            if (confElement.getQName().equals(new QName(locationTag))) {
                wadlLocation = confElement.getText();
                if(!wadlLocation.startsWith(RegistryConstants.PATH_SEPARATOR)){
                    wadlLocation = RegistryConstants.PATH_SEPARATOR + wadlLocation;
                }
                if(wadlLocation.endsWith(RegistryConstants.PATH_SEPARATOR)){
                    wadlLocation = wadlLocation.substring(0, wadlLocation.length() - 1);
                }
            }
        }
        this.wadlLocationConfiguration = locationConfiguration;
    }

    /**
     * @return createService
     */
    public boolean isCreateService() {
        return createService;
    }

    /**
     * Extracts createService property from the registry.xml
     *
     * @param createService createService property.
     */
    public void setCreateService(String createService) {
        this.createService = Boolean.valueOf(createService);
    }

    public void put(RequestContext requestContext) throws RegistryException {
        if (!CommonUtil.isUpdateLockAvailable()) {
            return;
        }
        CommonUtil.acquireUpdateLock();
        // setting up session local path map for mounted setup.
        boolean pathMapSet = setSessionLocalPathMap(requestContext);
        try {
            Resource resource = requestContext.getResource();
            String path = requestContext.getResourcePath().getPath();
            try {
                // If the WSDL is already there, we don't need to re-run this handler unless the content is changed.
                // Re-running this handler causes issues with downstream handlers and other behaviour (ex:- lifecycles).
                // If you need to do a replace programatically, delete-then-replace.
                if (requestContext.getRegistry().resourceExists(path)) {
                    // TODO: Add logic to compare content, and return only if the content didn't change.
                    return;
                }
            } catch (Exception ignore) { }
            try {
                if (resource != null) {
                    Object resourceContent = resource.getContent();
                    InputStream in = new ByteArrayInputStream((byte[]) resourceContent);
                    Stack<File> fileList = new Stack<File>();
                    List<String> uriList = new LinkedList<String>();
                    List<UploadTask> tasks = new LinkedList<UploadTask>();

                    int threadPoolSize = this.threadPoolSize;

                    File tempFile = File.createTempFile(tempFilePrefix, archiveExtension);
                    File tempDir = new File(tempFile.getAbsolutePath().substring(0,
                            tempFile.getAbsolutePath().length() - archiveExtension.length()));
                    try {
                        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile));
                        try {
                            byte[] contentChunk = new byte[1024];
                            int byteCount;
                            while ((byteCount = in.read(contentChunk)) != -1) {
                                out.write(contentChunk, 0, byteCount);
                            }
                            out.flush();
                        } finally {
                            out.close();
                        }
                        ZipEntry entry;

                        makeDir(tempDir);
                        ZipInputStream zs;
                        List<String> wsdlUriList = new LinkedList<String>();
                        List<String> xsdUriList = new LinkedList<String>();
                        List<String> wadlUriList = new LinkedList<String>();
                        List<String> swaggerUriList = new LinkedList<String>();
                        zs = new ZipInputStream(new FileInputStream(tempFile));
                        try {
                            entry = zs.getNextEntry();
                            while (entry != null) {
                                String entryName = entry.getName();
                                FileOutputStream os;
                                File file = new File(tempFile.getAbsolutePath().substring(0,
                                        tempFile.getAbsolutePath().length() -
                                                archiveExtension.length()) + File.separator + entryName);
                                if (entry.isDirectory()) {
                                    if (!file.exists()) {
                                        makeDirs(file);
                                        fileList.push(file);
                                    }
                                    entry = zs.getNextEntry();
                                    continue;
                                }
                                File parentFile = file.getParentFile();
                                if (!parentFile.exists()) {
                                    makeDirs(parentFile);
                                }
                                os = new FileOutputStream(file);
                                try {
                                    fileList.push(file);
                                    byte[] contentChunk = new byte[1024];
                                    int byteCount;
                                    while ((byteCount = zs.read(contentChunk)) != -1) {
                                        os.write(contentChunk, 0, byteCount);
                                    }
                                } finally {
                                    os.close();
                                }
                                zs.closeEntry();
                                entry = zs.getNextEntry();
                                if (entryName != null &&
                                        entryName.toLowerCase().endsWith(wsdlExtension)) {
                                    String uri = tempFile.toURI().toString();
                                    uri = uri.substring(0, uri.length() -
                                            archiveExtension.length()) + "/" + entryName;
                                    if (uri.startsWith("file:")) {
                                        uri = uri.substring(5);
                                    }
                                    while (uri.startsWith("/")) {
                                        uri = uri.substring(1);
                                    }
                                    uri = "file:///" + uri;
                                    if (uri.endsWith("/")) {
                                        uri = uri.substring(0, uri.length() -1);
                                    }
                                    wsdlUriList.add(uri);
                                } else if (entryName != null &&
                                        entryName.toLowerCase().endsWith(xsdExtension)) {
                                    String uri = tempFile.toURI().toString();
                                    uri = uri.substring(0, uri.length() -
                                            archiveExtension.length()) + "/" + entryName;
                                    if (uri.startsWith("file:")) {
                                        uri = uri.substring(5);
                                    }
                                    while (uri.startsWith("/")) {
                                        uri = uri.substring(1);
                                    }
                                    uri = "file:///" + uri;
                                    if (uri.endsWith("/")) {
                                        uri = uri.substring(0, uri.length() -1);
                                    }
                                    xsdUriList.add(uri);
                                } else if(entryName != null &&
                                        entryName.toLowerCase().endsWith(wadlExtension)){
                                    String uri = tempFile.toURI().toString();
                                    uri = uri.substring(0, uri.length() -
                                            archiveExtension.length()) + "/" + entryName;
                                    if (uri.startsWith("file:")) {
                                        uri = uri.substring(5);
                                    }
                                    while (uri.startsWith("/")) {
                                        uri = uri.substring(1);
                                    }
                                    uri = "file:///" + uri;
                                    if (uri.endsWith("/")) {
                                        uri = uri.substring(0, uri.length() -1);
                                    }
                                    wadlUriList.add(uri);
                                } else if(entryName != null &&
                                        entryName.toLowerCase().endsWith(swaggerExtension)){
                                    String uri = tempFile.toURI().toString();
                                    uri = uri.substring(0, uri.length() -
                                            archiveExtension.length()) + "/" + entryName;
                                    if (uri.startsWith("file:")) {
                                        uri = uri.substring(5);
                                    }
                                    while (uri.startsWith("/")) {
                                        uri = uri.substring(1);
                                    }
                                    uri = "file:///" + uri;
                                    if (uri.endsWith("/")) {
                                        uri = uri.substring(0, uri.length() -1);
                                    }
                                    swaggerUriList.add(uri);
                                } else if (entryName != null) {
                                    boolean isSkipFileExtension = false;
                                    for (String extension : skipFileExtensions) {
                                        if (entryName.toLowerCase().endsWith(extension.toLowerCase())) {
                                            isSkipFileExtension = true;
                                            break;
                                        }
                                    }
                                    if(!isSkipFileExtension){
                                        String uri = tempFile.toURI().toString();
                                        uri = uri.substring(0, uri.length() -
                                                archiveExtension.length()) + "/" + entryName;
                                        if (uri.startsWith("file:")) {
                                            uri = uri.substring(5);
                                        }
                                        while (uri.startsWith("/")) {
                                            uri = uri.substring(1);
                                        }
                                        uri = "file:///" + uri;
                                        if (uri.endsWith("/")) {
                                            uri = uri.substring(0, uri.length() -1);
                                        }
                                        uriList.add(uri);
                                    }
                                }
                            }
                        } finally {
                            zs.close();
                        }
                        Map<String, String> localPathMap = null;
                        if (CurrentSession.getLocalPathMap() != null) {
                            localPathMap =
                                    Collections.unmodifiableMap(CurrentSession.getLocalPathMap());
                        }
                        if (wsdlUriList.isEmpty() && xsdUriList.isEmpty() && wadlUriList.isEmpty() && uriList.isEmpty() && swaggerUriList.isEmpty()) {
                            throw new RegistryException(
                                    "No Files found in the given archive");
                        }
                        for (String uri : wsdlUriList) {
                            tasks.add(new UploadWSDLTask(requestContext, uri,
                                    CurrentSession.getTenantId(),
                                    CurrentSession.getUserRegistry(), CurrentSession.getUserRealm(),
                                    CurrentSession.getUser(), CurrentSession.getCallerTenantId(),
                                    localPathMap));
                        }
                        for (String uri : xsdUriList) {
                            tasks.add(new UploadXSDTask(requestContext, uri,
                                    CurrentSession.getTenantId(),
                                    CurrentSession.getUserRegistry(), CurrentSession.getUserRealm(),
                                    CurrentSession.getUser(), CurrentSession.getCallerTenantId(),
                                    localPathMap));
                        }
                        for (String uri : wadlUriList) {
                            tasks.add(new UploadWadlTask(requestContext, uri,
                                    CurrentSession.getTenantId(),
                                    CurrentSession.getUserRegistry(), CurrentSession.getUserRealm(),
                                    CurrentSession.getUser(), CurrentSession.getCallerTenantId(),
                                    localPathMap));
                        }
                        for (String uri : swaggerUriList) {
                            tasks.add(new UploadSwaggerTask(requestContext, uri,
                                    CurrentSession.getTenantId(),
                                    CurrentSession.getUserRegistry(), CurrentSession.getUserRealm(),
                                    CurrentSession.getUser(), CurrentSession.getCallerTenantId(),
                                    localPathMap));
                        }

                        String mediaType = resource.getProperty("registry.mediaType");
                        if (mediaType != null) {
                            for (String uri : uriList) {
                                tasks.add(new UploadFileTask(requestContext, uri,
                                        CurrentSession.getTenantId(),
                                        CurrentSession.getUserRegistry(), CurrentSession.getUserRealm(),
                                        CurrentSession.getUser(), CurrentSession.getCallerTenantId(),
                                        localPathMap, mediaType));
                            }
                            uriList.clear();
                        }

                        // calculate thread pool size for efficient use of resources in concurrent
                        // update scenarios.
                        int toAdd = wsdlUriList.size() + xsdUriList.size();
                        if (toAdd < threadPoolSize) {
                            if (toAdd < (threadPoolSize / 8)) {
                                threadPoolSize = 0;
                            } else if (toAdd < (threadPoolSize / 2)) {
                                threadPoolSize = (threadPoolSize / 8);
                            } else {
                                threadPoolSize = (threadPoolSize / 4);
                            }
                        }
                    } finally {
                        in.close();
                        resourceContent = null;
                        resource.setContent(null);
                    }
                    uploadFiles(tasks, tempFile, fileList, tempDir, threadPoolSize, path, uriList,
                            requestContext);
                }
            } catch (IOException e) {
                throw new RegistryException("Error occurred while unpacking Governance Archive", e);
            }
            if (Transaction.isRollbacked()) {
                throw new RegistryException("A nested transaction was rollbacked and therefore " +
                        "cannot proceed with this action.");
            }
            requestContext.setProcessingComplete(true);
        } finally {
            CommonUtil.releaseUpdateLock();
            removeSessionLocalPathMap(pathMapSet);
        }
    }

    /**
     * remove the Local PathMap from the CurrentSession
     * @param pathMapSet whether pathMap is set or not
     */
    private void removeSessionLocalPathMap(boolean pathMapSet) {
        if (pathMapSet) {
            CurrentSession.removeLocalPathMap();
        }
    }

    /**
     * Method will add Local PathMap to the CurrentSession, if it is not exists
     * Set ARCHIEVE_UPLOAD param to true, it param is accessed to calculate registry path in mounted env.
     * @param requestContext the request context to get mount points
     * @return whether pathMap is set or not
     */
    private boolean setSessionLocalPathMap(RequestContext requestContext) {
        boolean pathMapSet = false;
        if (CurrentSession.getLocalPathMap() == null) {
            RegistryContext registryContext = requestContext.getRegistry().getRegistryContext();
            if (registryContext != null && registryContext.getMounts() != null && !registryContext.getMounts().isEmpty()) {
                Map<String, String> localPathMap = new HashMap<String, String>();
                CurrentSession.setLocalPathMap(localPathMap);
                CurrentSession.getLocalPathMap().put(CommonConstants.ARCHIEVE_UPLOAD, "true");
                pathMapSet = true;
            }
        } else {
            CurrentSession.getLocalPathMap().put(CommonConstants.ARCHIEVE_UPLOAD, "true");
        }
        return pathMapSet;
    }

    /**
     * Method that runs the WSDL upload procedure.
     *
     * @param requestContext the request context for the import/put operation
     * @param uri the URL from which the WSDL is imported
     *
     * @return the path at which the WSDL was uploaded to
     *
     * @throws RegistryException if the operation failed.
     */
    protected String addWSDLFromZip(RequestContext requestContext, String uri)
            throws RegistryException {
        if (uri != null) {
            Resource local = requestContext.getRegistry().newResource();
            String version = requestContext.getResource().getProperty("version");
            local.setMediaType(wsdlMediaType);
            local.setProperty("version", version);
            local.setProperties(requestContext.getResource().getProperties());
            requestContext.setSourceURL(uri);
            requestContext.setResource(local);
            String path = requestContext.getResourcePath().getPath();
            if (path.lastIndexOf("/") != -1) {
                path = path.substring(0, path.lastIndexOf("/"));
            } else {
                path = "";
            }
            String wsdlName = uri;
            if (wsdlName.lastIndexOf("/") != -1) {
                wsdlName = wsdlName.substring(wsdlName.lastIndexOf("/"));
            } else {
                wsdlName = "/" + wsdlName;
            }
            path = path + wsdlName;
            requestContext.setResourcePath(new ResourcePath(path));
            WSDLProcessor wsdlProcessor = buildWSDLProcessor(requestContext, this.useOriginalSchema);
            String addedPath = wsdlProcessor.addWSDLToRegistry(requestContext, uri, local, false,
                    true, disableWSDLValidation,disableSymlinkCreation);
            if (CommonConstants.ENABLE.equals(System.getProperty(
                    CommonConstants.UDDI_SYSTEM_PROPERTY))) {
                AuthToken authToken = UDDIUtil.getPublisherAuthToken();
                if(authToken !=null){
                BusinessServiceInfo businessServiceInfo = new BusinessServiceInfo();
                WSDLInfo wsdlInfo = wsdlProcessor.getMasterWSDLInfo();
                businessServiceInfo.setServiceWSDLInfo(wsdlInfo);
                UDDIPublisher publisher = new UDDIPublisher();
                publisher.publishBusinessService(authToken,businessServiceInfo);
                }
            }
            log.debug("WSDL : " + addedPath);
            return addedPath;
        }
        return null;
    }

    /**
     * Method that runs the WADL upload procedure.
     *
     * @param requestContext requestContext the request context for the import/put operation
     * @param uri the URL from which the WADL is imported
     *
     * @return  the path at which the WADL was uploaded to
     *
     * @throws RegistryException if the operation failed.
     */
    protected String addWADLFromZip(RequestContext requestContext, String uri)
            throws RegistryException {
        if (uri != null) {
            Resource local = requestContext.getRegistry().newResource();
            String version = requestContext.getResource().getProperty("version");
            local.setMediaType(wadlMediaType);
            local.setProperty("version",version);
            requestContext.setSourceURL(uri);
            requestContext.setResource(local);
            String path = requestContext.getResourcePath().getPath();
            if (path.lastIndexOf("/") != -1) {
                path = path.substring(0, path.lastIndexOf("/"));
            } else {
                path = "";
            }
            String wadlName = uri;
            if (wadlName.lastIndexOf("/") != -1) {
                wadlName = wadlName.substring(wadlName.lastIndexOf("/"));
            } else {
                wadlName = "/" + wadlName;
            }
            path = path + wadlName;
            requestContext.setResourcePath(new ResourcePath(path));
            WADLProcessor wadlProcessor = new WADLProcessor (requestContext);
            wadlProcessor.setCreateService(isCreateService());
            return wadlProcessor.importWADLToRegistry(requestContext, disableWADLValidation);

        }
        return null;
    }

    /**
     * Method that runs the Swagger upload procedure.
     *
     * @param requestContext requestContext the request context for the import/put operation
     * @param uri the URL from which the swagger is imported
     *
     * @return  the path at which the Swagger was uploaded to
     *
     * @throws RegistryException if the operation failed.
     */
    protected String addSwaggerFromZip(RequestContext requestContext, String uri)
            throws RegistryException {
        if (uri != null) {
            Resource local = requestContext.getRegistry().newResource();
            String version = requestContext.getResource().getProperty("version");
            local.setMediaType(swaggerMediaType);
            local.setProperty("version",version);
            requestContext.setSourceURL(uri);
            requestContext.setResource(local);
            String path = requestContext.getResourcePath().getPath();
            if (path.lastIndexOf("/") != -1) {
                path = path.substring(0, path.lastIndexOf("/"));
            } else {
                path = "";
            }
            String swaggerName = uri;
            if (swaggerName.lastIndexOf("/") != -1) {
                swaggerName = swaggerName.substring(swaggerName.lastIndexOf("/"));
            } else {
                swaggerName = "/" + swaggerName;
            }
            path = path + swaggerName;
            requestContext.setResourcePath(new ResourcePath(path));
            SwaggerProcessor swaggerProcessor = new SwaggerProcessor (requestContext, isCreateService());
            InputStream inputStream = null;
            try {
                inputStream = new URL(uri).openStream();
                return swaggerProcessor.processSwagger(inputStream,
                        getChrootedSwaggerLocation(requestContext.getRegistryContext()), uri);
            } catch (IOException e) {
                throw new RegistryException("Swagger URI is invalid", e);
            } finally {
                if (inputStream != null){
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        log.warn("Error while deleting Swagger temp files");
                    }
                }
            }

        }
        return null;
    }

    /**
     * Method that runs the Schema upload procedure.
     *
     * @param requestContext the request context for the import/put operation
     * @param uri the URL from which the Schema is imported
     *
     * @return the path at which the schema was uploaded to
     *
     * @throws RegistryException if the operation failed.
     */
    protected String addSchemaFromZip(RequestContext requestContext, String uri)
            throws RegistryException {
        if (uri != null) {
            Resource local = requestContext.getRegistry().newResource();
            String version = requestContext.getResource().getProperty("version");
            local.setMediaType(xsdMediaType);
            local.setProperty("version",version);
            local.setProperties(requestContext.getResource().getProperties());
            requestContext.setSourceURL(uri);
            requestContext.setResource(local);
            String path = requestContext.getResourcePath().getPath();
            if (path.lastIndexOf("/") != -1) {
                path = path.substring(0, path.lastIndexOf("/"));
            } else {
                path = "";
            }
            String xsdName = uri;
            if (xsdName.lastIndexOf("/") != -1) {
                xsdName = xsdName.substring(xsdName.lastIndexOf("/"));
            } else {
                xsdName = "/" + xsdName;
            }
            path = path + xsdName;
            requestContext.setResourcePath(new ResourcePath(path));
            WSDLValidationInfo validationInfo = null;
            try {
                if (!disableSchemaValidation) {
                    validationInfo = SchemaValidator.validate(new XMLInputSource(null, uri, null));
                }
            } catch (Exception e) {
                throw new RegistryException("Exception occured while validating the schema" , e);
            }
            SchemaProcessor schemaProcessor =
                    buildSchemaProcessor(requestContext, validationInfo, this.useOriginalSchema);

            String addedPath = schemaProcessor
                    .importSchemaToRegistry(requestContext, path,
                            getChrootedSchemaLocation(requestContext.getRegistryContext()), true,disableSymlinkCreation);
            requestContext.setActualPath(addedPath);
            log.debug("XSD : " + addedPath);
            return addedPath;
        }
        return null;
    }

    private String getChrootedSchemaLocation(RegistryContext registryContext) {
        return RegistryUtils.getAbsolutePath(registryContext,
                RegistryConstants.GOVERNANCE_REGISTRY_BASE_PATH + schemaLocation);
    }

    private String getChrootedWADLLocation(RegistryContext registryContext) {
        return RegistryUtils.getAbsolutePath(registryContext,
                RegistryConstants.GOVERNANCE_REGISTRY_BASE_PATH + wadlLocation);
    }

    private String getChrootedSwaggerLocation(RegistryContext registryContext) {
        return RegistryUtils.getAbsolutePath(registryContext,
                        RegistryConstants.GOVERNANCE_REGISTRY_BASE_PATH + swaggerLocation);
    }

    public void importResource(RequestContext context) {
        // We don't support importing .gar files. This is meant only for uploading WSDL files
        // and imports from the local filesystem.
        log.warn("The imported Governance Web Archive will not be extracted. To extract the content"
                + " upload the archive from the file system.");
    }

    public void setWsdlMediaType(String wsdlMediaType) {
        this.wsdlMediaType = wsdlMediaType;
    }

    public void setWsdlExtension(String wsdlExtension) {
        this.wsdlExtension = wsdlExtension;
    }

    public void setSchemaMediaType(String xsdMediaType) {
        this.xsdMediaType = xsdMediaType;
    }

    public void setSchemaExtension(String xsdExtension) {
        this.xsdExtension = xsdExtension;
    }

    public void setArchiveExtension(String archiveExtension) {
        this.archiveExtension = archiveExtension;
    }

    public void setTempFilePrefix(String tempFilePrefix) {
        this.tempFilePrefix = tempFilePrefix;
    }

    public void setDisableWSDLValidation(String disableWSDLValidation) {
        this.disableWSDLValidation = Boolean.toString(true).equals(disableWSDLValidation);
    }

    public void setDisableSchemaValidation(String disableSchemaValidation) {
        this.disableSchemaValidation = Boolean.toString(true).equals(disableSchemaValidation);
    }

    public void setDisableWADLValidation(String disableWADLValidation) {
        this.disableWADLValidation = Boolean.getBoolean(disableWADLValidation);
    }

    public void setUseOriginalSchema(String useOriginalSchema) {
        this.useOriginalSchema = Boolean.toString(true).equals(useOriginalSchema);
    }

    public void setSkipFileExtensions(String skipFileExtensions) {
        if (skipFileExtensions != null) {
            String[] extensions = skipFileExtensions.split(this.extensionsSeparator);
            this.skipFileExtensions = Arrays.asList(extensions);
        }
    }

    /**
     * {@inheritDoc}
     */
    protected void onPutCompleted(String path, Map<String, String> addedResources,
                                  List<String> otherResources, RequestContext requestContext)
    //Final result printing in console.
            throws RegistryException {

    	Registry configRegistry = RegistryCoreServiceComponent.getRegistryService().getConfigSystemRegistry();
        String resourceName = RegistryUtils.getResourceName(requestContext.getResourcePath().getPath());

        OMFactory factory = OMAbstractFactory.getOMFactory();
        OMElement garElement = factory.createOMElement(
                    new QName(CommonConstants.REG_GAR_PATH_MAPPING_RESOURCE));
        garElement.addAttribute(factory.createOMAttribute(
                CommonConstants.REG_GAR_PATH_MAPPING_RESOURCE_ATTR_PATH, null, requestContext.getResourcePath().getPath()));


        log.info("Total Number of Files Uploaded: " + addedResources.size());
        List<String> failures = new LinkedList<String>();
        for (Map.Entry<String, String> e : addedResources.entrySet()) {
            if (e.getValue() == null) {
                failures.add(e.getKey());
                log.info("Failure " + failures.size() + ": " + e.getKey());
            } else {
                OMElement targetElement = factory.createOMElement(
                        new QName(CommonConstants.REG_GAR_PATH_MAPPING_RESOURCE_TARGET));
                targetElement.setText(e.getValue());
                garElement.addChild(targetElement);
            }
        }

        String pathMappingResourceName = resourceName;
        if (resourceName.contains(".")){
           pathMappingResourceName = resourceName.substring(0, resourceName.lastIndexOf("."));
        }

        String pathMappingResourcePath = CommonConstants.REG_GAR_PATH_MAPPING +
                pathMappingResourceName;
        boolean garMappingExists = configRegistry.resourceExists(pathMappingResourcePath);
        if (garMappingExists) {
            Resource pathMappingResource = configRegistry.get(pathMappingResourcePath);
            try {
                OMElement garMappingElement = AXIOMUtil.stringToOM(
                        new String((byte[]) pathMappingResource.getContent()));
                garMappingElement.addChild(garElement);
                pathMappingResource.setContent(garMappingElement.toString());
                configRegistry.put(pathMappingResourcePath, pathMappingResource);
            } catch (XMLStreamException e) {
                log.warn("Error occurred while retrieving the content of GAR mapping file ", e);
            }
        }

       log.info("Total Number of Files Failed to Upload: " + failures.size());
        if (otherResources.size() > 0) {
            log.info("Total Number of Files Not-Uploaded: " + otherResources.size());
        }
    }

    protected void uploadFiles(List<UploadTask> tasks,
                           File tempFile, Stack<File> fileList, File tempDir, int poolSize,
                           String path, List<String> uriList, RequestContext requestContext)
            throws RegistryException {
        CommonUtil.loadImportedArtifactMap();
        try {
            if (poolSize <= 0) {
                boolean updateLockAvailable = CommonUtil.isUpdateLockAvailable();
                if (!updateLockAvailable) {
                    CommonUtil.releaseUpdateLock();
                }
                try {
                    for (UploadTask task : tasks) {
                        task.run();
                    }
                } finally {
                    if (!updateLockAvailable) {
                        CommonUtil.acquireUpdateLock();
                    }
                }
            } else {
                ExecutorService executorService = Executors.newFixedThreadPool(poolSize);
                if (!CommonUtil.isArtifactIndexMapExisting()) {
                    CommonUtil.createArtifactIndexMap();
                }
                if (!CommonUtil.isSymbolicLinkMapExisting()) {
                    CommonUtil.createSymbolicLinkMap();
                }
                for (UploadTask task : tasks) {
                    executorService.submit(task);
                }
                executorService.shutdown();
                while (!executorService.isTerminated()) {

                }
            }
        } finally {
            CommonUtil.clearImportedArtifactMap();
        }
        try {
            if (CommonUtil.isArtifactIndexMapExisting()) {
                Map<String, String> artifactIndexMap =
                        CommonUtil.getAndRemoveArtifactIndexMap();

                if (log.isDebugEnabled()) {
                    for (Map.Entry<String, String> entry : artifactIndexMap.entrySet()) {
                        log.debug("Added Artifact Entry: " + entry.getKey());
                    }
                }

//                CommonUtil.addGovernanceArtifactEntriesWithRelativeValues(
//                        CommonUtil.getUnchrootedSystemRegistry(requestContext), artifactIndexMap);
            }
            Registry registry = requestContext.getRegistry();
            if (!isDisableSymlinkCreation() && CommonUtil.isSymbolicLinkMapExisting()) {
                Map<String, String> symbolicLinkMap =
                        CommonUtil.getAndRemoveSymbolicLinkMap();

                for (Map.Entry<String, String> entry : symbolicLinkMap.entrySet()) {
                    if (log.isDebugEnabled()) {
                        log.debug("Added Symbolic Link: " + entry.getKey());
                    }
                    try {
                        if (registry.resourceExists(entry.getKey())) {
                            registry.removeLink(entry.getKey());
                        }
                    } catch (RegistryException ignored) {
                        // we are not bothered above errors in getting rid of symbolic links.
                    }
                    requestContext.getSystemRegistry().createLink(entry.getKey(), entry.getValue());
                }
            }
        } catch (RegistryException e) {
            log.error("Unable to build artifact index.", e);
        }
        Map<String, String> taskResults = new LinkedHashMap<String, String>();
        for (UploadTask task : tasks) {
            if (task.getFailed()) {
                taskResults.put(task.getUri(), null);
            } else {
                taskResults.put(task.getUri(), task.getResult());
            }
        }
        onPutCompleted(path, taskResults, uriList, requestContext);
        try {
            delete(tempFile);
            while (!fileList.isEmpty()) {
                delete(fileList.pop());
            }
            FileUtils.deleteDirectory(tempDir);
        } catch (IOException e) {
            log.error("Unable to cleanup temporary files", e);
        }
        log.info("Completed uploading files from archive file");
    }

    protected static abstract class UploadTask implements Runnable {

        private String uri;
        private RequestContext requestContext;
        private int tenantId = -1;
        private UserRegistry userRegistry;
        private UserRealm userRealm;
        private String userId;
        private int callerTenantId;
        private Map<String, String> localPathMap;
        private Random random = new Random(10);

        protected String result = null;
        protected boolean failed = false;
        protected int retries = 0;

        public UploadTask(RequestContext requestContext, String uri, int tenantId,
                          UserRegistry userRegistry, UserRealm userRealm, String userId,
                          int callerTenantId, Map<String, String> localPathMap) {
            this.userRegistry = userRegistry;
            this.userRealm = userRealm;
            this.tenantId = tenantId;
            this.requestContext = requestContext;
            this.uri = uri;
            this.userId = userId;
            this.callerTenantId = callerTenantId;
            this.localPathMap = localPathMap;
        }

        public void run() {
            try {
                PrivilegedCarbonContext.startTenantFlow();
                //This is for fixing CARBON-14469.
                PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantId(tenantId, true);
                //set user name to set in swagger import provider
                PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(userId);
                // File is already uploaded via wsdl or xsd imports those are skip
                if (CommonUtil.isImportedArtifactExisting(new File(uri).toString())) {
                    failed = false;
                    result = "added from import";
                    return;
                }
                doWork();
            } finally {
                PrivilegedCarbonContext.endTenantFlow();
            }
        }

        protected void retry() {
            //Number of retry can be configurable via handler configuration (<property name="numberOfRetry">1</property>)
            if (retries < ZipWSDLMediaTypeHandler.numberOfRetry) {
                ++retries;
                log.info("Retrying to upload resource: " + uri);
                int i = random.nextInt(10);
                if (log.isDebugEnabled()) {
                    log.debug("Waiting for " + i + " seconds");
                }
                try {
                    Thread.sleep(1000 * i);
                } catch (InterruptedException ignored) {
                }
                doWork();
            } else {
                failed = true;
            }
        }

        private void doWork() {
            CurrentSession.setTenantId(tenantId);
            CurrentSession.setUserRegistry(userRegistry);
            CurrentSession.setUserRealm(userRealm);
            CurrentSession.setUser(userId);
            CurrentSession.setCallerTenantId(callerTenantId);
            if (localPathMap != null) {
                CurrentSession.setLocalPathMap(localPathMap);
            }
            try {
                if (CommonUtil.isUpdateLockAvailable()) {
                    CommonUtil.acquireUpdateLock();
                    try {
                        RequestContext requestContext =
                                new RequestContext(this.requestContext.getRegistry(),
                                        this.requestContext.getRepository(),
                                        this.requestContext.getVersionRepository());
                        requestContext.setResourcePath(this.requestContext.getResourcePath());
                        requestContext.setResource(this.requestContext.getResource());
                        requestContext.setOldResource(this.requestContext.getOldResource());
                        doProcessing(requestContext, uri);
                    } finally {
                        CommonUtil.releaseUpdateLock();
                    }
                }
            } catch (RegistryException e) {
                log.error("An error occurred while  uploading "+uri, e);
                retry();
            } catch (RuntimeException e) {
                log.error("An unhandled exception occurred while  uploading " + uri, e);
                retry();
            } finally {
                CurrentSession.removeUser();
                CurrentSession.removeUserRealm();
                CurrentSession.removeUserRegistry();
                CurrentSession.removeTenantId();
                CurrentSession.removeCallerTenantId();
                if (localPathMap != null) {
                    CurrentSession.removeLocalPathMap();
                }
                // get rid of the reference to the request context at the end.
                requestContext = null;
            }
        }

        protected abstract void doProcessing(RequestContext requestContext, String uri)
                throws RegistryException;

        public String getUri() {
            return uri;
        }

        public String getResult() {
            return result;
        }

        public boolean getFailed() {
            return failed;
        }
    }

    protected class UploadFileTask extends UploadTask {

        String mediaType;

        public UploadFileTask(RequestContext requestContext, String uri, int tenantId,
                             UserRegistry userRegistry, UserRealm userRealm, String userId,
                             int callerTenantId, Map<String, String> localPathMap,
                             String mediaType) {
            super(requestContext, uri, tenantId, userRegistry, userRealm, userId, callerTenantId,
                    localPathMap);
            this.mediaType = mediaType;
        }

        protected void doProcessing(RequestContext requestContext, String uri)
                throws RegistryException {
            Registry registry = requestContext.getRegistry();
            Resource resource = registry.newResource();
            String version = requestContext.getResource().getProperty("version");
            if (resource.getUUID() == null) {
                resource.setUUID(UUID.randomUUID().toString());
            }
            if (version != null) {
                resource.setProperty("version", version);
            }
            resource.setMediaType(this.mediaType);
            InputStream inputStream;
            try {
                inputStream = new URL(uri).openStream();
            } catch (IOException e) {
                throw new RegistryException("The URL " + uri + " is incorrect.", e);
            }
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            int nextChar;
            try {
                while ((nextChar = inputStream.read()) != -1) {
                    outputStream.write(nextChar);
                }
                outputStream.flush();
            } catch (IOException e) {
                throw new RegistryException("Failed to read content from URL " + uri, e);
            }
            resource.setContent(outputStream.toByteArray());
            String path = RegistryUtils.getParentPath(requestContext.getResourcePath().getPath()) +
                    RegistryConstants.PATH_SEPARATOR + RegistryUtils.getResourceName(uri);
            result = registry.put(path, resource);
        }
    }

    protected class UploadXSDTask extends UploadTask {

        public UploadXSDTask(RequestContext requestContext, String uri, int tenantId,
                          UserRegistry userRegistry, UserRealm userRealm, String userId,
                          int callerTenantId, Map<String, String> localPathMap) {
            super(requestContext, uri, tenantId, userRegistry, userRealm, userId, callerTenantId,
                    localPathMap);
        }

        protected void doProcessing(RequestContext requestContext, String uri)
                throws RegistryException {
            result = addSchemaFromZip(requestContext, uri);
        }
    }

    protected class UploadWSDLTask extends UploadTask {

        public UploadWSDLTask(RequestContext requestContext, String uri, int tenantId,
                          UserRegistry userRegistry, UserRealm userRealm, String userId,
                          int callerTenantId, Map<String, String> localPathMap) {
            super(requestContext, uri, tenantId, userRegistry, userRealm, userId, callerTenantId,
                    localPathMap);
        }

        protected void doProcessing(RequestContext requestContext, String uri)
                throws RegistryException {
            result = addWSDLFromZip(requestContext, uri);
        }
    }

    protected class UploadWadlTask extends UploadTask {

        public UploadWadlTask(RequestContext requestContext, String uri, int tenantId,
                              UserRegistry userRegistry, UserRealm userRealm, String userId,
                              int callerTenantId, Map<String, String> localPathMap) {
            super(requestContext, uri, tenantId, userRegistry, userRealm, userId, callerTenantId,
                    localPathMap);

        }

        protected void doProcessing(RequestContext requestContext, String uri) throws RegistryException {
            result = addWADLFromZip(requestContext, uri);
        }
    }

    protected class UploadSwaggerTask extends UploadTask {

        public UploadSwaggerTask(RequestContext requestContext, String uri, int tenantId,
                              UserRegistry userRegistry, UserRealm userRealm, String userId,
                              int callerTenantId, Map<String, String> localPathMap) {
            super(requestContext, uri, tenantId, userRegistry, userRealm, userId, callerTenantId,
                    localPathMap);

        }

        protected void doProcessing(RequestContext requestContext, String uri) throws RegistryException {
            result = addSwaggerFromZip(requestContext, uri);
        }
    }
}
