/*
* 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.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;
/**
* GroovyScript2RestLoader allows to save a Groovy script in JCR.
*
* @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 SessionProviderService 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, SessionProviderService 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, SessionProviderService 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, SessionProviderService 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<String> iterator = delayedWorkspacePublishing.iterator(); iterator.hasNext();)
{
String workspaceName = 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 javax.ws.rs.core.UriInfo
* @param repository repository name
* @param workspace workspace name
* @param path path to resource to be created
* @return Response with status 'created'
* @request
* {code}
* "stream" : the input stream that contains groovy source code
* {code}
* @LevelAPI Provisional
*/
@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.
* 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 org.apache.commons.fileupload.FileItem
* @param uriInfo javax.ws.rs.core.UriInfo
* @param repository repository name
* @param workspace workspace name
* @param path path to resource to be created
* @return Response with status 'created'
* @request
* {code}
* "items" : the sending data with HTML form ('multipart/form-data')
* {code}
* @LevelAPI Provisional
*/
@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.
* NOTE : To be able load Groovy source files from specified
* folders the following rules must be observed:
*
* - Groovy source files must be located in folder with respect
* to package structure
* - Name of Groovy source files must be the same as name of
* class located in file
* - Groovy source file must have extension '.groovy'
*
* Example: If source stream that we want validate contains the
* following code:
*
*
* package c.b.a
*
* import a.b.c.A
*
* class B extends A {
* // Do something.
* }
*
* 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
* @request
* {code }
* "script" : the Groovy source stream.
* {code}
* @LevelAPI Provisional
*/
@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
* @LevelAPI Provisional
*/
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 javax.ws.rs.core.UriInfo
* @param repository repository name
* @param workspace workspace name
* @param path path to resource to be created
* @return Response with status 'created'
* @request
* {code }
* "stream" : the input stream that contains groovy source code.
* {code}
* @LevelAPI Provisional
*/
@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.
* 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 org.apache.commons.fileupload.FileItem
* @param uriInfo see javax.ws.rs.core.UriInfo
* @param repository repository name
* @param workspace workspace name
* @param path path to resource to be created
* @return Response with status 'created'
* @request
* {code}
* "items" : the sending data with HTML form ('multipart/form-data')
* {code}
* @LevelAPI Provisional
*/
@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 state is
* true 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.
* NOTE: To be able load Groovy source files from specified
* folders the following rules must be observed:
*
* - Groovy source files must be located in folder with respect
* to package structure
* - Name of Groovy source files must be the same as name of
* class located in file
* - Groovy source file must have extension '.groovy'
*
* Example: If source stream that we want validate contains the
* following code:
*
*
* package c.b.a
*
* import a.b.c.A
*
* class B extends A {
* // Do something.
* }
*
* 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
* @LevelAPI Provisional
*/
@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
* @LevelAPI Provisional
*/
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
* @LevelAPI Provisional
*/
@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.
* Example: .../scripts/groovy/test1.groovy/load is the same to
* .../scripts/groovy/test1.groovy/load?state=true
* @LevelAPI Provisional
*/
@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
* @response
* {code}
* "scriptsource" : the source code of groovy script.
* {code}
* @LevelAPI Provisional
*/
@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
* @response
* {code:json}
* "scriptList" : the groovy script's meta-information
* {code}
* @LevelAPI Provisional
*/
@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
* @response
* {code:json}
* "scriptList" : the list of all groovy scripts found in workspace.
* {code}
* @LevelAPI Provisional
*/
@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 scriptList the list of scripts
*/
public ScriptList(List<String> scriptList)
{
this.list = scriptList;
}
}
}