org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader.java

Source

/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.services.jcr.ext.script.groovy;

import org.apache.commons.fileupload.FileItem;
import org.codehaus.groovy.control.CompilationFailedException;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.container.component.ComponentPlugin;
import org.exoplatform.container.configuration.ConfigurationManager;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.container.xml.ObjectParameter;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.ext.app.SessionProviderService;
import org.exoplatform.services.jcr.ext.app.ThreadLocalSessionProviderService;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.jcr.ext.registry.RegistryEntry;
import org.exoplatform.services.jcr.ext.registry.RegistryService;
import org.exoplatform.services.jcr.ext.resource.UnifiedNodeReference;
import org.exoplatform.services.jcr.impl.core.query.lucene.IndexOfflineRepositoryException;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.rest.ext.groovy.GroovyClassLoaderProvider;
import org.exoplatform.services.rest.ext.groovy.GroovyJaxrsPublisher;
import org.exoplatform.services.rest.ext.groovy.MalformedScriptException;
import org.exoplatform.services.rest.ext.groovy.ResourceId;
import org.exoplatform.services.rest.ext.groovy.SourceFile;
import org.exoplatform.services.rest.ext.groovy.SourceFolder;
import org.exoplatform.services.rest.impl.ResourceBinder;
import org.exoplatform.services.rest.impl.ResourcePublicationException;
import org.exoplatform.services.script.groovy.GroovyScriptInstantiator;
import org.picocontainer.Startable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

import javax.annotation.security.RolesAllowed;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
 * @author <a href="mailto:andrew00x@gmail.com">Andrey Parfonov</a>
 * @version $Id: GroovyScript2RestLoader.java 34445 2009-07-24 07:51:18Z
 *          dkatayev $
 */
@SuppressWarnings("deprecation")
@Path("script/groovy")
public class GroovyScript2RestLoader implements Startable {
    protected GroovyJaxrsPublisher groovyPublisher;

    protected static class InnerGroovyJaxrsPublisher extends GroovyJaxrsPublisher {
        public InnerGroovyJaxrsPublisher(ResourceBinder binder, GroovyScriptInstantiator instantiator,
                GroovyClassLoaderProvider classLoaderProvider) {
            super(binder, instantiator, classLoaderProvider);
        }
    }

    /** Logger. */
    private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.ext.GroovyScript2RestLoader");

    /** Default node types for Groovy scripts. */
    private static final String DEFAULT_NODETYPE = "exo:groovyResourceContainer";

    /** Service name. */
    private static final String SERVICE_NAME = "GroovyScript2RestLoader";

    private static final int DELAYED_AUTOLOAD_TIMEOUT = 20000; // 20 sec

    /** See {@link InitParams}. */
    protected InitParams initParams;

    /** See {@link RepositoryService}. */
    protected RepositoryService repositoryService;

    /** See {@link ConfigurationManager}. */
    protected ConfigurationManager configurationManager;

    /** See {@link RegistryService}. */
    protected RegistryService registryService;

    /** See {@link SessionProviderService} */
    protected ThreadLocalSessionProviderService sessionProviderService;

    /** Keeps configuration for observation listener. */
    private ObservationListenerConfiguration observationListenerConfiguration;

    //protected GroovyJaxrsPublisher groovyPublisher;

    protected List<GroovyScript2RestLoaderPlugin> loadPlugins;

    protected List<GroovyScriptAddRepoPlugin> addRepoPlugins;

    /** See {@link ResourceBinder}. */
    private ResourceBinder binder;

    /** Node type for Groovy scripts. */
    private String nodeType;

    /**
     * @param binder binder for RESTful services
     * @param groovyScriptInstantiator instantiate groovy scripts
     * @param repositoryService See {@link RepositoryService}
     * @param sessionProviderService See {@link SessionProviderService}
     * @param configurationManager for solve resource loading issue in common way
     * @param params initialized parameters
     */
    public GroovyScript2RestLoader(ResourceBinder binder, GroovyScriptInstantiator groovyScriptInstantiator,
            RepositoryService repositoryService, ThreadLocalSessionProviderService sessionProviderService,
            ConfigurationManager configurationManager,
            org.exoplatform.services.jcr.ext.resource.jcr.Handler jcrUrlHandler, InitParams params) {
        this(binder, groovyScriptInstantiator, repositoryService, sessionProviderService, configurationManager,
                null,
                new InnerGroovyJaxrsPublisher(binder, groovyScriptInstantiator, new JcrGroovyClassLoaderProvider()),
                jcrUrlHandler, params);
    }

    /**
     * @param binder binder for RESTful services
     * @param groovyScriptInstantiator instantiates Groovy scripts
     * @param repositoryService See {@link RepositoryService}
     * @param sessionProviderService See {@link SessionProviderService}
     * @param configurationManager for solve resource loading issue in common way
     * @param registryService See {@link RegistryService}
     * @param params initialized parameters
     */
    public GroovyScript2RestLoader(ResourceBinder binder, GroovyScriptInstantiator groovyScriptInstantiator,
            RepositoryService repositoryService, ThreadLocalSessionProviderService sessionProviderService,
            ConfigurationManager configurationManager, RegistryService registryService,
            org.exoplatform.services.jcr.ext.resource.jcr.Handler jcrUrlHandler, InitParams params) {
        this(binder, groovyScriptInstantiator, repositoryService, sessionProviderService, configurationManager,
                registryService,
                new InnerGroovyJaxrsPublisher(binder, groovyScriptInstantiator, new JcrGroovyClassLoaderProvider()),
                jcrUrlHandler, params);
    }

    public GroovyScript2RestLoader(ResourceBinder binder, GroovyScriptInstantiator groovyScriptInstantiator,
            RepositoryService repositoryService, ThreadLocalSessionProviderService sessionProviderService,
            ConfigurationManager configurationManager, RegistryService registryService,
            GroovyJaxrsPublisher groovyPublisher,
            org.exoplatform.services.jcr.ext.resource.jcr.Handler jcrUrlHandler, InitParams params) {
        this.groovyPublisher = groovyPublisher;
        this.binder = binder;
        this.repositoryService = repositoryService;
        this.configurationManager = configurationManager;
        this.registryService = registryService;
        this.sessionProviderService = sessionProviderService;
        this.groovyPublisher = groovyPublisher;
        this.initParams = params;
    }

    /**
     * Get node type for store scripts, may throw {@link IllegalStateException}
     * if <tt>nodeType</tt> not initialized yet.
     * 
     * @return return node type
     */
    public String getNodeType() {
        if (nodeType == null) {
            throw new IllegalStateException("Node type not initialized, yet. ");
        }
        return nodeType;
    }

    /**
     * @see org.picocontainer.Startable#start()
     */
    public void start() {
        if (registryService != null && initParams != null
                && !registryService.getForceXMLConfigurationValue(initParams)) {
            SessionProvider sessionProvider = SessionProvider.createSystemProvider();
            try {
                readParamsFromRegistryService(sessionProvider);
            } catch (Exception e) {
                readParamsFromFile();
                try {
                    writeParamsToRegistryService(sessionProvider);
                } catch (Exception exc) {
                    LOG.error("Cannot write init configuration to RegistryService.", exc);
                }
            } finally {
                sessionProvider.close();
            }
        } else {
            readParamsFromFile();
        }

        // Add script from configuration files to JCR.
        addScripts();

        if (addRepoPlugins != null && addRepoPlugins.size() > 0) {
            try {
                Set<URL> repos = new HashSet<URL>();
                for (GroovyScriptAddRepoPlugin pl : addRepoPlugins) {
                    repos.addAll(pl.getRepositories());
                }
                this.groovyPublisher.getGroovyClassLoader()
                        .setResourceLoader(new JcrGroovyResourceLoader(repos.toArray(new URL[repos.size()])));
            } catch (MalformedURLException e) {
                LOG.error("Unable add groovy script repository. ", e);
            }
        }

        if (observationListenerConfiguration != null) {
            try {
                // Deploy auto-load scripts and start Observation Listeners.
                final String repositoryName = getWorkingRepositoryName();

                List<String> workspaceNames = observationListenerConfiguration.getWorkspaces();

                final ManageableRepository repository = repositoryService.getRepository(repositoryName);

                // JCR it offers an asynchronous workspace reindexing (since 1.14.0-CR2). But while it
                // is performed in background queries can't be executed. In this case autoload scripts could only
                // be loaded after reindexing finished.
                final Set<String> delayedWorkspacePublishing = new HashSet<String>();

                for (String workspaceName : workspaceNames) {
                    Session session = repository.getSystemSession(workspaceName);
                    try {
                        autoLoadScripts(session);
                    } catch (IndexOfflineRepositoryException e) {
                        delayedWorkspacePublishing.add(workspaceName);
                    } finally {
                        session.logout();
                    }

                    session.getWorkspace().getObservationManager().addEventListener(
                            new GroovyScript2RestUpdateListener(repository, workspaceName, this),
                            Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED, "/", true, null,
                            new String[] { getNodeType() }, false);
                }
                if (!delayedWorkspacePublishing.isEmpty()) {
                    LOG.warn("The following workspaces are being reindexed now: " + delayedWorkspacePublishing
                            + ". Groove scripts from those workspaces marked as AutoLoad will be loaded later.");
                    // lauch delayed autoLoad
                    new Thread(new Runnable() {
                        public void run() {
                            while (true) {
                                if (delayedWorkspacePublishing.isEmpty()) {
                                    // finish thread
                                    return;
                                }
                                for (Iterator iterator = delayedWorkspacePublishing.iterator(); iterator
                                        .hasNext();) {
                                    String workspaceName = (String) iterator.next();
                                    try {
                                        Session session = repository.getSystemSession(workspaceName);
                                        try {
                                            autoLoadScripts(session);
                                        } finally {
                                            session.logout();
                                        }

                                        // if no exception, then remove item from set
                                        iterator.remove();
                                    } catch (IndexOfflineRepositoryException e) {
                                        if (LOG.isTraceEnabled()) {
                                            LOG.trace("An exception occurred: " + e.getMessage());
                                        }
                                    } catch (Exception e) {
                                        // skip
                                        LOG.error("An exception occurred: " + e.getMessage());
                                    }
                                }
                                try {
                                    Thread.sleep(DELAYED_AUTOLOAD_TIMEOUT);
                                } catch (InterruptedException e) {
                                    // skip
                                }
                            }
                        }
                    }, "GrooveSrciptDelayedAutoLoader-" + repositoryName).start();
                }
            } catch (Exception e) {
                LOG.error("Error occurs ", e);
            }
        }

        // Finally bind this object as RESTful service.
        // NOTE this service does not implement ResourceContainer, as usually
        // done for this type of services. It can't be binded in common way cause
        // to dependencies problem. And in other side not possible to use third
        // part which can be injected by GroovyScript2RestLoader.
        binder.addResource(this, null);
    }

    private void autoLoadScripts(Session session) throws RepositoryException {
        String workspaceName = session.getWorkspace().getName();
        String repositoryName = getWorkingRepositoryName();

        String xpath = "//element(*, " + getNodeType() + ")[@exo:autoload='true']";
        Query query = session.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH);

        QueryResult result = query.execute();
        NodeIterator nodeIterator = result.getNodes();
        while (nodeIterator.hasNext()) {
            Node node = nodeIterator.nextNode();

            if (node.getPath().startsWith("/jcr:system")) {
                continue;
            }

            try {
                groovyPublisher.publishPerRequest(node.getProperty("jcr:data").getStream(),
                        new NodeScriptKey(repositoryName, workspaceName, node), null);
            } catch (CompilationFailedException e) {
                LOG.error(e.getMessage(), e);
            } catch (ResourcePublicationException e) {
                LOG.error(e.getMessage(), e);
            }
        }
    }

    /**
     * @see org.picocontainer.Startable#stop()
     */
    public void stop() {
        // nothing to do!
    }

    public void addPlugin(ComponentPlugin cp) {
        if (cp instanceof GroovyScript2RestLoaderPlugin) {
            if (loadPlugins == null) {
                loadPlugins = new ArrayList<GroovyScript2RestLoaderPlugin>();
            }
            loadPlugins.add((GroovyScript2RestLoaderPlugin) cp);
        }
        if (cp instanceof GroovyScriptAddRepoPlugin) {
            if (addRepoPlugins == null) {
                addRepoPlugins = new ArrayList<GroovyScriptAddRepoPlugin>();
            }
            addRepoPlugins.add((GroovyScriptAddRepoPlugin) cp);
        }
    }

    /**
     * Add scripts that specified in configuration.
     */
    protected void addScripts() {
        if (loadPlugins == null || loadPlugins.size() == 0) {
            return;
        }
        for (GroovyScript2RestLoaderPlugin loadPlugin : loadPlugins) {
            // If no one script configured then skip this item,
            // there is no reason to do anything.
            if (loadPlugin.getXMLConfigs().size() == 0) {
                continue;
            }

            Session session = null;
            try {
                ManageableRepository repository = repositoryService.getRepository(loadPlugin.getRepository());
                String workspace = loadPlugin.getWorkspace();
                session = repository.getSystemSession(workspace);
                String nodeName = loadPlugin.getNode();
                Node node = null;
                try {
                    node = (Node) session.getItem(nodeName);
                } catch (PathNotFoundException e) {
                    StringTokenizer tokens = new StringTokenizer(nodeName, "/");
                    node = session.getRootNode();
                    while (tokens.hasMoreTokens()) {
                        String t = tokens.nextToken();
                        if (node.hasNode(t)) {
                            node = node.getNode(t);
                        } else {
                            node = node.addNode(t, "nt:folder");
                        }
                    }
                }

                for (XMLGroovyScript2Rest xg : loadPlugin.getXMLConfigs()) {
                    String scriptName = xg.getName();
                    if (node.hasNode(scriptName)) {
                        LOG.warn("Node '" + node.getPath() + "/" + scriptName + "' already exists. ");
                        continue;
                    }

                    createScript(node, scriptName, xg.isAutoload(),
                            configurationManager.getInputStream(xg.getPath()));
                }
                session.save();
            } catch (Exception e) {
                LOG.error("Failed add scripts. ", e);
            } finally {
                if (session != null) {
                    session.logout();
                }
            }
        }
    }

    /**
     * Create JCR node.
     * 
     * @param parent parent node
     * @param name name of node to be created
     * @param stream data stream for property jcr:data
     * @return newly created node
     * @throws Exception if any errors occurs
     */
    protected Node createScript(Node parent, String name, boolean autoload, InputStream stream) throws Exception {
        Node scriptFile = parent.addNode(name, "nt:file");
        Node script = scriptFile.addNode("jcr:content", getNodeType());
        script.setProperty("exo:autoload", autoload);
        script.setProperty("jcr:mimeType", "script/groovy");
        script.setProperty("jcr:lastModified", Calendar.getInstance());
        script.setProperty("jcr:data", stream);
        return scriptFile;
    }

    /**
     * Read parameters from RegistryService.
     * 
     * @param sessionProvider the SessionProvider
     * @throws RepositoryException
     * @throws PathNotFoundException
     */
    protected void readParamsFromRegistryService(SessionProvider sessionProvider)
            throws PathNotFoundException, RepositoryException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("<<< Read init parametrs from registry service.");
        }

        observationListenerConfiguration = new ObservationListenerConfiguration();

        String entryPath = RegistryService.EXO_SERVICES + "/" + SERVICE_NAME + "/" + "nodeType";
        RegistryEntry registryEntry = registryService.getEntry(sessionProvider, entryPath);
        Document doc = registryEntry.getDocument();
        Element element = doc.getDocumentElement();
        nodeType = getAttributeSmart(element, "value");

        entryPath = RegistryService.EXO_SERVICES + "/" + SERVICE_NAME + "/" + "repository";
        registryEntry = registryService.getEntry(sessionProvider, entryPath);
        doc = registryEntry.getDocument();
        element = doc.getDocumentElement();
        observationListenerConfiguration.setRepository(getAttributeSmart(element, "value"));

        entryPath = RegistryService.EXO_SERVICES + "/" + SERVICE_NAME + "/" + "workspaces";
        registryEntry = registryService.getEntry(sessionProvider, entryPath);
        doc = registryEntry.getDocument();
        element = doc.getDocumentElement();
        String workspaces = getAttributeSmart(element, "value");

        String ws[] = workspaces.split(";");
        List<String> wsList = new ArrayList<String>();
        for (String w : ws) {
            wsList.add(w);
        }

        observationListenerConfiguration.setWorkspaces(wsList);

        LOG.info("NodeType from RegistryService: " + getNodeType());
        LOG.info(
                "Repository name from RegistryService: " + (observationListenerConfiguration.getRepository() != null
                        ? observationListenerConfiguration.getRepository()
                        : "not configured, will be used the current one"));

        LOG.info("List of workspaces from RegistryService: " + observationListenerConfiguration.getWorkspaces());
    }

    /**
     * Write parameters to RegistryService.
     * 
     * @param sessionProvider the SessionProvider
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IOException
     * @throws RepositoryException
     */
    protected void writeParamsToRegistryService(SessionProvider sessionProvider)
            throws IOException, SAXException, ParserConfigurationException, RepositoryException {
        if (LOG.isDebugEnabled()) {
            LOG.debug(">>> Save init parametrs in registry service.");
        }

        Document doc;
        try {
            doc = SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<Document>() {
                public Document run() throws ParserConfigurationException {
                    return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
                }
            });
        } catch (PrivilegedActionException e) {
            throw (ParserConfigurationException) e.getCause();
        }

        Element root = doc.createElement(SERVICE_NAME);
        doc.appendChild(root);

        Element element = doc.createElement("nodeType");
        setAttributeSmart(element, "value", getNodeType());
        root.appendChild(element);

        StringBuffer sb = new StringBuffer();
        for (String workspace : observationListenerConfiguration.getWorkspaces()) {
            if (sb.length() > 0) {
                sb.append(';');
            }
            sb.append(workspace);
        }
        element = doc.createElement("workspaces");
        setAttributeSmart(element, "value", sb.toString());
        root.appendChild(element);

        element = doc.createElement("repository");
        setAttributeSmart(element, "value", observationListenerConfiguration.getRepository());
        root.appendChild(element);

        RegistryEntry serviceEntry = new RegistryEntry(doc);
        registryService.createEntry(sessionProvider, RegistryService.EXO_SERVICES, serviceEntry);
    }

    /**
     * Get attribute value.
     * 
     * @param element The element to get attribute value
     * @param attr The attribute name
     * @return Value of attribute if present and null in other case
     */
    protected String getAttributeSmart(Element element, String attr) {
        return element.hasAttribute(attr) ? element.getAttribute(attr) : null;
    }

    /**
     * Set attribute value. If value is null the attribute will be removed.
     * 
     * @param element The element to set attribute value
     * @param attr The attribute name
     * @param value The value of attribute
     */
    protected void setAttributeSmart(Element element, String attr, String value) {
        if (value == null) {
            element.removeAttribute(attr);
        } else {
            element.setAttribute(attr, value);
        }
    }

    /**
     * Read parameters from file.
     */
    protected void readParamsFromFile() {
        if (initParams != null) {
            nodeType = initParams.getValuesParam("nodetype") != null
                    ? initParams.getValueParam("nodetype").getValue()
                    : DEFAULT_NODETYPE;

            ObjectParameter param = initParams.getObjectParam("observation.config");
            observationListenerConfiguration = (ObservationListenerConfiguration) param.getObject();
        } else {
            nodeType = DEFAULT_NODETYPE;
        }

        LOG.info("NodeType from configuration file: " + getNodeType());
        if (observationListenerConfiguration != null) {
            LOG.info("Repository name from configuration file: "
                    + (observationListenerConfiguration.getRepository() != null
                            ? observationListenerConfiguration.getRepository()
                            : "not configured, will be used the current one"));

            LOG.info("List of workspaces from configuration file: "
                    + observationListenerConfiguration.getWorkspaces());
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////

    /**
     * This method is useful for clients that can send script in request body
     * without form-data. At required to set specific Content-type header
     * 'script/groovy'.
     * 
     * @param stream the stream that contains groovy source code
     * @param uriInfo see {@link UriInfo}
     * @param repository repository name
     * @param workspace workspace name
     * @param path path to resource to be created
     * @return Response with status 'created'
     */
    @POST
    @Consumes({ "script/groovy" })
    @Path("add/{repository}/{workspace}/{path:.*}")
    public Response addScript(InputStream stream, @Context UriInfo uriInfo,
            @PathParam("repository") String repository, @PathParam("workspace") String workspace,
            @PathParam("path") String path) {
        Session ses = null;
        try {
            ses = sessionProviderService.getSessionProvider(null).getSession(workspace,
                    repositoryService.getRepository(repository));
            Node node = (Node) ses.getItem(getPath(path));
            createScript(node, getName(path), false, stream);
            ses.save();
            URI location = uriInfo.getBaseUriBuilder().path(getClass(), "getScript").build(repository, workspace,
                    path);
            return Response.created(location).build();
        } catch (PathNotFoundException e) {
            String msg = "Path " + path + " does not exists";
            LOG.error(msg);
            return Response.status(Response.Status.NOT_FOUND).entity(msg).entity(MediaType.TEXT_PLAIN).build();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
                    .type(MediaType.TEXT_PLAIN).build();
        } finally {
            if (ses != null) {
                ses.logout();
            }
        }
    }

    /**
     * This method is useful for clients that send scripts as file in
     * 'multipart/*' request body. <br/>
     * NOTE even we use iterator item should be only one, rule one address - one
     * script. This method is created just for comfort loading script from HTML
     * form. NOT use this script for uploading few files in body of
     * 'multipart/form-data' or other type of multipart.
     * 
     * @param items iterator {@link FileItem}
     * @param uriInfo see {@link UriInfo}
     * @param repository repository name
     * @param workspace workspace name
     * @param path path to resource to be created
     * @return Response with status 'created'
     */
    @POST
    @Consumes({ "multipart/*" })
    @Path("add/{repository}/{workspace}/{path:.*}")
    public Response addScript(Iterator<FileItem> items, @Context UriInfo uriInfo,
            @PathParam("repository") String repository, @PathParam("workspace") String workspace,
            @PathParam("path") String path) {
        Session ses = null;
        try {
            ses = sessionProviderService.getSessionProvider(null).getSession(workspace,
                    repositoryService.getRepository(repository));
            Node node = (Node) ses.getItem(getPath(path));
            InputStream stream = null;
            boolean autoload = false;
            while (items.hasNext()) {
                FileItem fitem = items.next();
                if (fitem.isFormField() && fitem.getFieldName() != null
                        && fitem.getFieldName().equalsIgnoreCase("autoload")) {
                    autoload = Boolean.valueOf(fitem.getString());
                } else if (!fitem.isFormField()) {
                    stream = fitem.getInputStream();
                }
            }

            createScript(node, getName(path), autoload, stream);
            ses.save();
            URI location = uriInfo.getBaseUriBuilder().path(getClass(), "getScript").build(repository, workspace,
                    path);
            return Response.created(location).build();
        } catch (PathNotFoundException e) {
            String msg = "Path " + path + " does not exists";
            LOG.error(msg);
            return Response.status(Response.Status.NOT_FOUND).entity(msg).entity(MediaType.TEXT_PLAIN).build();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
                    .type(MediaType.TEXT_PLAIN).build();
        } finally {
            if (ses != null) {
                ses.logout();
            }
        }
    }

    /**
     * Check is specified source <code>script</code> contains valid Groovy source
     * code.
     * 
     * @param name script name. This name will be used by GroovyClassLoader to
     *           identify script, e.g. specified name will be used in error
     *           message in compilation of Groovy fails. If this parameter is
     *           <code>null</code> then GroovyClassLoader will use automatically
     *           generated name
     * @param script Groovy source stream
     * @param sources locations (string representation of URL) of source folders
     *           that should be add in class path when compile Groovy script.
     *           <b>NOTE</b> To be able load Groovy source files from specified
     *           folders the following rules must be observed:
     *           <ul>
     *           <li>Groovy source files must be located in folder with respect
     *           to package structure</li>
     *           <li>Name of Groovy source files must be the same as name of
     *           class located in file</li>
     *           <li>Groovy source file must have extension '.groovy'</li>
     *           </ul>
     * <br/>
     *           Example: If source stream that we want validate contains the
     *           following code:
     * 
     *           <pre>
     *           package c.b.a
     *           
     *           import a.b.c.A
     *           
     *           class B extends A {
     *           // Do something.
     *           }
     * </pre>
     * 
     *           Assume we store dependencies in JCR then URL of folder with
     *           Groovy sources may be like this:
     *           <code>jcr://repository/workspace#/groovy-library</code>. Then
     *           absolute path to JCR node that contains Groovy source must be as
     *           following: <code>/groovy-library/a/b/c/A.groovy</code>
     * @param files locations (string representation of URL) of source files that
     *           should be add in class path when compile Groovy script. Each
     *           location must point directly to file that contains Groovy
     *           source. Source file can have any name and extension
     * @return Response with corresponded status. 200 if source code is valid
     */
    @POST
    @Consumes({ "script/groovy" })
    @Path("validate{name:.*}")
    public Response validateScript(@PathParam("name") String name, final InputStream script,
            @QueryParam("sources") List<String> sources, @QueryParam("file") List<String> files) {
        try {
            validateScript(name, script, createSourceFolders(sources), createSourceFiles(files));
            return Response.ok().build();
        } catch (MalformedScriptException e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).type(MediaType.TEXT_PLAIN)
                    .build();
        } catch (MalformedURLException e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).type(MediaType.TEXT_PLAIN)
                    .build();
        }
    }

    /**
     * Check is specified source <code>script</code> contains valid Groovy source
     * code.
     * 
     * @param name script name. This name will be used by GroovyClassLoader to
     *           identify script, e.g. specified name will be used in error
     *           message in compilation of Groovy fails. If this parameter is
     *           <code>null</code> then GroovyClassLoader will use automatically
     *           generated name
     * @param script Groovy source stream
     * @param src set of folders that contains Groovy source files that should be
     *           add in class-path when validate <code>script</code>, see
     *           {@link SourceFolder#getPath()}. <b>NOTE</b> To be able load
     *           Groovy source files from specified folders the following rules
     *           must be observed:
     *           <ul>
     *           <li>Groovy source files must be located in folder with respect
     *           to package structure</li>
     *           <li>Name of Groovy source files must be the same as name of
     *           class located in file</li>
     *           <li>Groovy source file must have extension '.groovy'</li>
     *           </ul>
     * @param files set of groovy source files that should be add in class-path
     *           when validate <code>script</code>. Each item must point directly
     *           to file that contains Groovy source, see
     *           {@link SourceFile#getPath()} . Source file can have any name and
     *           extension
     * @throws MalformedScriptException if <code>script</code> contains not valid
     *            source code
     */
    public void validateScript(String name, InputStream script, SourceFolder[] src, SourceFile[] files)
            throws MalformedScriptException {
        if (name != null && name.length() > 0 && name.startsWith("/")) {
            name = name.substring(1);
        }
        groovyPublisher.validateResource(script, name, src, files);
    }

    /**
     * This method is useful for clients that can send script in request body
     * without form-data. At required to set specific Content-type header
     * 'script/groovy'.
     * 
     * @param stream the stream that contains groovy source code
     * @param uriInfo see {@link UriInfo}
     * @param repository repository name
     * @param workspace workspace name
     * @param path path to resource to be created
     * @return Response with status 'created'
     */
    @POST
    @Consumes({ "script/groovy" })
    @Path("update/{repository}/{workspace}/{path:.*}")
    public Response updateScript(InputStream stream, @Context UriInfo uriInfo,
            @PathParam("repository") String repository, @PathParam("workspace") String workspace,
            @PathParam("path") String path) {
        Session ses = null;
        try {
            ses = sessionProviderService.getSessionProvider(null).getSession(workspace,
                    repositoryService.getRepository(repository));
            Node node = (Node) ses.getItem("/" + path);
            node.getNode("jcr:content").setProperty("jcr:data", stream);
            ses.save();
            URI location = uriInfo.getBaseUriBuilder().path(getClass(), "getScript").build(repository, workspace,
                    path);
            return Response.created(location).build();
        } catch (PathNotFoundException e) {
            String msg = "Path " + path + " does not exists";
            LOG.error(msg);
            return Response.status(Response.Status.NOT_FOUND).entity(msg).entity(MediaType.TEXT_PLAIN).build();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
                    .type(MediaType.TEXT_PLAIN).build();
        } finally {
            if (ses != null) {
                ses.logout();
            }
        }
    }

    /**
     * This method is useful for clients that send scripts as file in
     * 'multipart/*' request body. <br/>
     * NOTE even we use iterator item should be only one, rule one address - one
     * script. This method is created just for comfort loading script from HTML
     * form. NOT use this script for uploading few files in body of
     * 'multipart/form-data' or other type of multipart.
     * 
     * @param items iterator {@link FileItem}
     * @param uriInfo see {@link UriInfo}
     * @param repository repository name
     * @param workspace workspace name
     * @param path path to resource to be created
     * @return Response with status 'created'
     */
    @POST
    @Consumes({ "multipart/*" })
    @Path("update/{repository}/{workspace}/{path:.*}")
    public Response updateScript(Iterator<FileItem> items, @Context UriInfo uriInfo,
            @PathParam("repository") String repository, @PathParam("workspace") String workspace,
            @PathParam("path") String path) {
        Session ses = null;
        try {
            FileItem fitem = items.next();
            InputStream stream = null;
            if (!fitem.isFormField()) {
                stream = fitem.getInputStream();
            }
            ses = sessionProviderService.getSessionProvider(null).getSession(workspace,
                    repositoryService.getRepository(repository));
            Node node = (Node) ses.getItem("/" + path);
            node.getNode("jcr:content").setProperty("jcr:data", stream);
            ses.save();
            URI location = uriInfo.getBaseUriBuilder().path(getClass(), "getScript").build(repository, workspace,
                    path);
            return Response.created(location).build();
        } catch (PathNotFoundException e) {
            String msg = "Path " + path + " does not exists";
            LOG.error(msg);
            return Response.status(Response.Status.NOT_FOUND).entity(msg).entity(MediaType.TEXT_PLAIN).build();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
                    .type(MediaType.TEXT_PLAIN).build();
        } finally {
            if (ses != null) {
                ses.logout();
            }
        }
    }

    /**
     * Deploy groovy script as REST service. If this property set to 'true' then
     * script will be deployed as REST service if 'false' the script will be
     * undeployed. NOTE is script already deployed and <tt>state</tt> is
     * <tt>true</tt> script will be re-deployed.
     * 
     * @param repository repository name
     * @param workspace workspace name
     * @param path the path to JCR node that contains groovy script to be
     *           deployed
     * @param state <code>true</code> if resource should be loaded and
     *           <code>false</code> otherwise. If this attribute is not present
     *           in HTTP request then it will be considered as <code>true</code>
     * @param sources locations (string representation of URL) of source folders
     *           that should be add in class path when compile Groovy script.
     *           <b>NOTE</b> To be able load Groovy source files from specified
     *           folders the following rules must be observed:
     *           <ul>
     *           <li>Groovy source files must be located in folder with respect
     *           to package structure</li>
     *           <li>Name of Groovy source files must be the same as name of
     *           class located in file</li>
     *           <li>Groovy source file must have extension '.groovy'</li>
     *           </ul>
     * <br/>
     *           Example: If source stream that we want validate contains the
     *           following code:
     * 
     *           <pre>
     *           package c.b.a
     *           
     *           import a.b.c.A
     *           
     *           class B extends A {
     *           // Do something.
     *           }
     * </pre>
     * 
     *           Assume we store dependencies in JCR then URL of folder with
     *           Groovy sources may be like this:
     *           <code>jcr://repository/workspace#/groovy-library</code>. Then
     *           absolute path to JCR node that contains Groovy source must be as
     *           following: <code>/groovy-library/a/b/c/A.groovy</code>
     * @param files locations (string representation of URL) of source files that
     *           should be add in class path when compile Groovy script. Each
     *           location must point directly to file that contains Groovy
     *           source. Source file can have any name and extension
     * @param properties optional properties to be applied to loaded resource.
     *           Ignored if <code>state</code> parameter is false
     */
    @POST
    @Path("load/{repository}/{workspace}/{path:.*}")
    @RolesAllowed({ "administrators" })
    public Response load(@PathParam("repository") String repository, @PathParam("workspace") String workspace,
            @PathParam("path") String path, @DefaultValue("true") @QueryParam("state") boolean state,
            @QueryParam("sources") List<String> sources, @QueryParam("file") List<String> files,
            MultivaluedMap<String, String> properties) {
        try {
            return load(repository, workspace, path, state, properties, createSourceFolders(sources),
                    createSourceFiles(files));
        } catch (MalformedURLException e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).type(MediaType.TEXT_PLAIN)
                    .build();
        }
    }

    /**
     * Deploy groovy script as REST service. If this property set to 'true' then
     * script will be deployed as REST service if 'false' the script will be
     * undeployed. NOTE is script already deployed and <tt>state</tt> is
     * <tt>true</tt> script will be re-deployed.
     * 
     * @param repository repository name
     * @param workspace workspace name
     * @param path the path to JCR node that contains groovy script to be
     *           deployed
     * @param state <code>true</code> if resource should be loaded and
     *           <code>false</code> otherwise. If this attribute is not present
     *           in HTTP request then it will be considered as <code>true</code>
     * @param properties optional properties to be applied to loaded resource.
     *           Ignored if <code>state</code> parameter is false
     * @param src set of folders that contains Groovy source files that should be
     *           add in class-path when compile file located at <code>path</code>
     *           . <b>NOTE</b> To be able load Groovy source files from specified
     *           folders the following rules must be observed:
     *           <ul>
     *           <li>Groovy source files must be located in folder with respect
     *           to package structure</li>
     *           <li>Name of Groovy source files must be the same as name of
     *           class located in file</li>
     *           <li>Groovy source file must have extension '.groovy'</li>
     *           </ul>
     * @param files set of groovy source files that should be add in class-path
     *           when compile file located at <code>path</code>. Each item must
     *           point directly to file that contains Groovy source, see
     *           {@link SourceFile#getPath()} . Source file can have any name and
     *           extension
     */
    public Response load(String repository, String workspace, String path, boolean state,
            MultivaluedMap<String, String> properties, SourceFolder[] src, SourceFile[] files) {
        Session ses = null;
        try {
            ses = sessionProviderService.getSessionProvider(null).getSession(workspace,
                    repositoryService.getRepository(repository));
            Node script = ((Node) ses.getItem("/" + path)).getNode("jcr:content");
            ResourceId key = new NodeScriptKey(repository, workspace, script);
            if (state) {
                groovyPublisher.unpublishResource(key);
                groovyPublisher.publishPerRequest(script.getProperty("jcr:data").getStream(), key, properties, src,
                        files);
            } else {
                if (null == groovyPublisher.unpublishResource(key)) {
                    return Response.status(Response.Status.BAD_REQUEST)
                            .entity("Can't unbind script " + path
                                    + ", not bound or has wrong mapping to the resource class ")
                            .type(MediaType.TEXT_PLAIN).build();
                }
            }
            return Response.status(Response.Status.NO_CONTENT).build();
        } catch (CompilationFailedException e) {
            return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).type(MediaType.TEXT_PLAIN)
                    .build();
        } catch (ResourcePublicationException e) {
            return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).type(MediaType.TEXT_PLAIN)
                    .build();
        } catch (PathNotFoundException e) {
            String msg = "Path " + path + " does not exists";
            LOG.error(msg);
            return Response.status(Response.Status.NOT_FOUND).entity(msg).type(MediaType.TEXT_PLAIN).build();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
                    .type(MediaType.TEXT_PLAIN).build();
        } finally {
            if (ses != null) {
                ses.logout();
            }
        }
    }

    /**
     * Remove node that contains groovy script.
     * 
     * @param repository repository name
     * @param workspace workspace name
     * @param path JCR path to node that contains script
     */
    @POST
    @Path("delete/{repository}/{workspace}/{path:.*}")
    public Response deleteScript(@PathParam("repository") String repository,
            @PathParam("workspace") String workspace, @PathParam("path") String path) {
        Session ses = null;
        try {
            ses = sessionProviderService.getSessionProvider(null).getSession(workspace,
                    repositoryService.getRepository(repository));
            ses.getItem("/" + path).remove();
            ses.save();
            return Response.status(Response.Status.NO_CONTENT).build();
        } catch (PathNotFoundException e) {
            String msg = "Path " + path + " does not exists";
            LOG.error(msg);
            return Response.status(Response.Status.NOT_FOUND).entity(msg).entity(MediaType.TEXT_PLAIN).build();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
                    .type(MediaType.TEXT_PLAIN).build();
        } finally {
            if (ses != null) {
                ses.logout();
            }
        }
    }

    /**
     * Change exo:autoload property. If this property is 'true' script will be
     * deployed automatically when JCR repository startup and automatically
     * re-deployed when script source code changed.
     * 
     * @param repository repository name
     * @param workspace workspace name
     * @param path JCR path to node that contains script
     * @param state value for property exo:autoload, if it is not specified then
     *           'true' will be used as default. <br />
     *           Example: .../scripts/groovy/test1.groovy/load is the same to
     *           .../scripts/groovy/test1.groovy/load?state=true
     */
    @POST
    @Path("autoload/{repository}/{workspace}/{path:.*}")
    public Response autoload(@PathParam("repository") String repository, @PathParam("workspace") String workspace,
            @PathParam("path") String path, @DefaultValue("true") @QueryParam("state") boolean state) {
        Session ses = null;
        try {
            ses = sessionProviderService.getSessionProvider(null).getSession(workspace,
                    repositoryService.getRepository(repository));
            Node script = ((Node) ses.getItem("/" + path)).getNode("jcr:content");
            script.setProperty("exo:autoload", state);
            ses.save();
            return Response.status(Response.Status.NO_CONTENT).build();
        } catch (PathNotFoundException e) {
            String msg = "Path " + path + " does not exists";
            LOG.error(msg);
            return Response.status(Response.Status.NOT_FOUND).entity(msg).entity(MediaType.TEXT_PLAIN).build();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
                    .type(MediaType.TEXT_PLAIN).build();
        } finally {
            if (ses != null) {
                ses.logout();
            }
        }
    }

    /**
     * Get source code of groovy script.
     * 
     * @param repository repository name
     * @param workspace workspace name
     * @param path JCR path to node that contains script
     * @return groovy script as stream
     */
    @POST
    @Produces({ "script/groovy" })
    @Path("src/{repository}/{workspace}/{path:.*}")
    public Response getScript(@PathParam("repository") String repository, @PathParam("workspace") String workspace,
            @PathParam("path") String path) {
        Session ses = null;
        try {
            ses = sessionProviderService.getSessionProvider(null).getSession(workspace,
                    repositoryService.getRepository(repository));
            Node scriptFile = (Node) ses.getItem("/" + path);
            return Response.status(Response.Status.OK)
                    .entity(scriptFile.getNode("jcr:content").getProperty("jcr:data").getStream())
                    .type("script/groovy").build();
        } catch (PathNotFoundException e) {
            String msg = "Path " + path + " does not exists";
            LOG.error(msg);
            return Response.status(Response.Status.NOT_FOUND).entity(msg).entity(MediaType.TEXT_PLAIN).build();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
                    .type(MediaType.TEXT_PLAIN).build();
        } finally {
            if (ses != null) {
                ses.logout();
            }
        }
    }

    /**
     * Get groovy script's meta-information.
     * 
     * @param repository repository name
     * @param workspace workspace name
     * @param path JCR path to node that contains script
     * @return groovy script's meta-information
     */
    @POST
    @Produces({ MediaType.APPLICATION_JSON })
    @Path("meta/{repository}/{workspace}/{path:.*}")
    public Response getScriptMetadata(@PathParam("repository") String repository,
            @PathParam("workspace") String workspace, @PathParam("path") String path) {
        Session ses = null;
        try {
            ses = sessionProviderService.getSessionProvider(null).getSession(workspace,
                    repositoryService.getRepository(repository));
            Node script = ((Node) ses.getItem("/" + path)).getNode("jcr:content");
            ResourceId key = new NodeScriptKey(repository, workspace, script);

            ScriptMetadata meta = new ScriptMetadata(script.getProperty("exo:autoload").getBoolean(), //
                    groovyPublisher.isPublished(key), //
                    script.getProperty("jcr:mimeType").getString(), //
                    script.getProperty("jcr:lastModified").getDate().getTimeInMillis());
            return Response.status(Response.Status.OK).entity(meta).type(MediaType.APPLICATION_JSON).build();
        } catch (PathNotFoundException e) {
            String msg = "Path " + path + " does not exists";
            LOG.error(msg);
            return Response.status(Response.Status.NOT_FOUND).entity(msg).entity(MediaType.TEXT_PLAIN).build();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
                    .type(MediaType.TEXT_PLAIN).build();
        } finally {
            if (ses != null) {
                ses.logout();
            }
        }
    }

    /**
     * Returns the list of all groovy-scripts found in workspace.
     * 
     * @param repository repository name
     * @param workspace workspace name
     * @param name additional search parameter. If not empty method returns the
     *           list of script names matching wildcard else returns all the
     *           scripts found in workspace.
     * @return list of groovy services
     */
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Path("list/{repository}/{workspace}")
    public Response list(@PathParam("repository") String repository, @PathParam("workspace") String workspace,
            @QueryParam("name") String name) {
        Session ses = null;
        try {
            ses = sessionProviderService.getSessionProvider(null).getSession(workspace,
                    repositoryService.getRepository(repository));

            String xpath = "//element(*, exo:groovyResourceContainer)";

            Query query = ses.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH);
            QueryResult result = query.execute();
            NodeIterator nodeIterator = result.getNodes();

            ArrayList<String> scriptList = new ArrayList<String>();

            if (name == null || name.length() == 0) {
                while (nodeIterator.hasNext()) {
                    Node node = nodeIterator.nextNode();
                    scriptList.add(node.getParent().getPath());
                }
            } else {
                StringBuilder p = new StringBuilder();
                // add '.*' pattern at the start
                p.append(".*");
                for (int i = 0; i < name.length(); i++) {
                    char c = name.charAt(i);
                    if (c == '*' || c == '?') {
                        p.append('.');
                    }
                    if (".()[]^$|".indexOf(c) != -1) {
                        p.append('\\');
                    }
                    p.append(c);
                }
                // add '.*' pattern at he end
                p.append(".*");

                Pattern pattern = Pattern.compile(p.toString(), Pattern.CASE_INSENSITIVE);
                while (nodeIterator.hasNext()) {
                    Node node = nodeIterator.nextNode();
                    String scriptName = node.getParent().getPath();

                    if (pattern.matcher(scriptName).matches()) {
                        scriptList.add(scriptName);
                    }
                }
            }
            Collections.sort(scriptList);
            return Response.status(Response.Status.OK).entity(new ScriptList(scriptList))
                    .type(MediaType.APPLICATION_JSON).build();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
                    .type(MediaType.TEXT_PLAIN).build();
        } finally {
            if (ses != null) {
                ses.logout();
            }
        }
    }

    private SourceFolder[] createSourceFolders(List<String> sources) throws MalformedURLException {
        SourceFolder[] src = null;
        if (sources != null && sources.size() > 0) {
            src = new SourceFolder[sources.size()];
            for (int i = 0; i < sources.size(); i++) {
                String str = sources.get(i);
                URL url = null;
                if (str.startsWith("jcr://")) {
                    url = new URL(null, str, UnifiedNodeReference.getURLStreamHandler());
                } else {
                    url = new URL(str);
                }
                src[i] = new SourceFolder(url);
            }
        }
        return src;
    }

    private SourceFile[] createSourceFiles(List<String> files) throws MalformedURLException {
        SourceFile[] srcFiles = null;
        if (files != null && files.size() > 0) {
            srcFiles = new SourceFile[files.size()];
            for (int i = 0; i < files.size(); i++) {
                String str = files.get(i);
                URL url = null;
                if (str.startsWith("jcr://")) {
                    url = new URL(null, str, UnifiedNodeReference.getURLStreamHandler());
                } else {
                    url = new URL(str);
                }
                srcFiles[i] = new SourceFile(url);
            }
        }
        return srcFiles;
    }

    /**
     * Get working repository name. Returns the repository name from configuration 
     * if it previously configured and returns the name of current repository in other case.
     *  
     * @return String
     *           repository name
     * @throws RepositoryException
     */
    private String getWorkingRepositoryName() throws RepositoryException {
        if (observationListenerConfiguration.getRepository() == null) {
            return repositoryService.getCurrentRepository().getConfiguration().getName();
        } else {
            return observationListenerConfiguration.getRepository();
        }
    }

    /**
     * Extract path to node's parent from full path.
     * 
     * @param fullPath full path to node
     * @return node's parent path
     */
    protected static String getPath(String fullPath) {
        int sl = fullPath.lastIndexOf('/');
        return sl > 0 ? "/" + fullPath.substring(0, sl) : "/";
    }

    /**
     * Extract node's name from full node path.
     * 
     * @param fullPath full path to node
     * @return node's name
     */
    protected static String getName(String fullPath) {
        int sl = fullPath.lastIndexOf('/');
        return sl >= 0 ? fullPath.substring(sl + 1) : fullPath;
    }

    /**
     * Script meta-data, used for pass script meta-data as JSON.
     */
    public static class ScriptMetadata {
        /** Is script autoload. */
        private final boolean autoload;

        /** Is script loaded. */
        private final boolean load;

        /** Script media type (script/groovy). */
        private final String mediaType;

        /** Last modified date. */
        private final long lastModified;

        public ScriptMetadata(boolean autoload, boolean load, String mediaType, long lastModified) {
            this.autoload = autoload;
            this.load = load;
            this.mediaType = mediaType;
            this.lastModified = lastModified;
        }

        /**
         * @return {@link #autoload}
         */
        public boolean getAutoload() {
            return autoload;
        }

        /**
         * @return {@link #load}
         */
        public boolean getLoad() {
            return load;
        }

        /**
         * @return {@link #mediaType}
         */
        public String getMediaType() {
            return mediaType;
        }

        /**
         * @return {@link #lastModified}
         */
        public long getLastModified() {
            return lastModified;
        }
    }

    /**
     * Script list, used for pass script list as JSON.
     */
    public static class ScriptList {
        /** The list of scripts. */
        private List<String> list;

        /**
         * @return the list of scripts.
         */
        public List<String> getList() {
            return list;
        }

        /**
         * @param the list of scripts
         */
        public ScriptList(List<String> scriptList) {
            this.list = scriptList;
        }
    }
}