From ac9960758f260a9ad969a606a6cc98c48c2aa63d Mon Sep 17 00:00:00 2001 From: David Caruana Date: Tue, 5 Jun 2007 10:41:51 +0000 Subject: [PATCH] Web Scripts: - Addition of reponse status code and template support - Appropriate status codes added to login & ticket web scripts - Web Script Index page also mapped to / url - Various fixes applied to url to web script mapping git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5846 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/messages/webscripts.properties | 87 ++++++- .../org/alfresco/index.get.desc.xml | 1 + .../alfresco/templates/webscripts/status.ftl | 33 +++ .../web-scripts-application-context.xml | 13 + .../web/scripts/AbstractWebScript.java | 227 +++++++++++++++++- .../web/scripts/DeclarativeWebScript.java | 115 +++++---- .../scripts/DeclarativeWebScriptRegistry.java | 83 ++++++- .../web/scripts/TemplateProcessor.java | 23 ++ .../web/scripts/TestWebScriptServer.java | 38 ++- .../web/scripts/WebScriptException.java | 17 ++ .../alfresco/web/scripts/WebScriptMatch.java | 18 +- .../web/scripts/WebScriptRequestImpl.java | 74 +++--- .../web/scripts/WebScriptResponse.java | 11 + .../web/scripts/WebScriptRuntime.java | 210 ++++++++++------ .../web/scripts/WebScriptServletRequest.java | 3 +- .../web/scripts/WebScriptServletResponse.java | 25 +- .../alfresco/web/scripts/WebScriptStatus.java | 178 ++++++++++++++ .../org/alfresco/web/scripts/bean/Index.java | 4 +- .../web/scripts/bean/IndexPackage.java | 4 +- .../alfresco/web/scripts/bean/IndexURI.java | 4 +- .../web/scripts/bean/IndexUpdate.java | 4 +- .../web/scripts/bean/KeywordSearch.java | 4 +- .../org/alfresco/web/scripts/bean/Login.java | 27 ++- .../web/scripts/bean/LoginTicket.java | 23 +- .../web/scripts/bean/LoginTicketDelete.java | 31 ++- .../web/scripts/bean/SearchEngines.java | 4 +- .../web/scripts/jsf/WebScriptJSFResponse.java | 15 ++ .../portlet/WebScriptPortletResponse.java | 23 +- 28 files changed, 1081 insertions(+), 218 deletions(-) create mode 100644 config/alfresco/templates/webscripts/status.ftl create mode 100644 source/java/org/alfresco/web/scripts/WebScriptStatus.java diff --git a/config/alfresco/messages/webscripts.properties b/config/alfresco/messages/webscripts.properties index f79e5bd3a8..fe82a3a1cb 100644 --- a/config/alfresco/messages/webscripts.properties +++ b/config/alfresco/messages/webscripts.properties @@ -1 +1,86 @@ -testserver.help=alfresco/messages/webscripts-test-help.txt \ No newline at end of file +## +## Web Script Response Codes +## +webscript.code.100.name=Continue +webscript.code.100.description=Client can continue. +webscript.code.101.name=Switching Protocols +webscript.code.101.description=The server is switching protocols according to Upgrade header. +webscript.code.200.name=OK +webscript.code.200.description=The request succeeded normally. +webscript.code.201.name=Created +webscript.code.201.description=Request succeeded and created a new resource on the server. +webscript.code.202.name=Accepted +webscript.code.202.description=Request was accepted for processing, but was not completed. +webscript.code.203.name=Non authoritative information +webscript.code.203.description=The meta information presented by the client did not originate from the server. +webscript.code.204.name=No ContentSC_NO_CONTENT +webscript.code.204.description=The request succeeded but that there was no new information to return. +webscript.code.205.name=Reset Content +webscript.code.205.description=The agent SHOULD reset the document view which caused the request to be sent. +webscript.code.206.name=Partial Content +webscript.code.206.description=The server has fulfilled the partial GET request for the resource. +webscript.code.300.name=Multiple Choices +webscript.code.300.description=The requested resource corresponds to any one of a set of representations, each with its own specific location. +webscript.code.301.name=Moved Permanently +webscript.code.301.description=The resource has permanently moved to a new location, and that future references should use a new URI with their requests. +webscript.code.302.name=Moved Temporarily +webscript.code.302.description=The resource has temporarily moved to another location, but that future references should still use the original URI to access the resource. +webscript.code.303.name=See Other +webscript.code.303.description=The response to the request can be found under a different URI. +webscript.code.304.name=Not Modified +webscript.code.304.description=A conditional GET operation found that the resource was available and not modified. +webscript.code.305.name=Use Proxy +webscript.code.305.description=The requested resource MUST be accessed through the proxy given by the Location field. +webscript.code.400.name=Bad Request +webscript.code.400.description=Request sent by the client was syntactically incorrect. +webscript.code.401.name=Unauthorized +webscript.code.401.description=The request requires HTTP authentication. +webscript.code.402.name=Payment Required +webscript.code.402.description=Reserved for future use. +webscript.code.403.name=Forbidden +webscript.code.403.description=Server understood the request but refused to fulfill it. +webscript.code.404.name=Not Found +webscript.code.404.description=Requested resource is not available. +webscript.code.405.name=Method Not Allowed +webscript.code.405.description=The method specified in the Request-Line is not allowed for the resource identified by the Request-URI. +webscript.code.406.name=Not Acceptable +webscript.code.406.description=The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request. +webscript.code.407.name=Proxy Authentication Required +webscript.code.407.description=The client MUST first authenticate itself with the proxy. +webscript.code.408.name=Request Timeout +webscript.code.408.description=The client did not produce a request within the time that the server was prepared to wait. +webscript.code.409.name=Conflict +webscript.code.409.description=Request could not be completed due to a conflict with the current state of the resource. +webscript.code.410.name=Gone +webscript.code.410.description=Resource is no longer available at the server and no forwarding address is known. +webscript.code.411.name=Length required +webscript.code.411.description=Request cannot be handled without a defined Content-Length. +webscript.code.412.name=Precondition Failed +webscript.code.412.description=The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server. +webscript.code.413.name=Request Entity Too Large +webscript.code.413.description=The server is refusing to process the request because the request entity is larger than the server is willing or able to process. +webscript.code.414.name=Request URI Too Long +webscript.code.414.description=The server is refusing to service the request because the Request-URI is longer than the server is willing to interpret. +webscript.code.415.name=Unsupported Media Type +webscript.code.415.description=The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method. +webscript.code.416.name=Requested Range Not Satisfiable +webscript.code.416.description=The server cannot serve the requested byte range. +webscript.code.417.name=Expectation Failed +webscript.code.417.description=Server could not meet the expectation given in the Expect request header. +webscript.code.500.name=Internal Error +webscript.code.500.description=An error inside the HTTP server which prevented it from fulfilling the request. +webscript.code.501.name=Not Implemented +webscript.code.501.description=The HTTP server does not support the functionality needed to fulfill the request. +webscript.code.502.name=Bad Gateway +webscript.code.502.description=HTTP server received an invalid response from a server it consulted when acting as a proxy or gateway. +webscript.code.503.name=Service Unavailable +webscript.code.503.description=The HTTP server is temporarily overloaded, and unable to handle the request. +webscript.code.504.name=Gateway Timeout +webscript.code.504.description=Server did not receive a timely response from the upstream server while acting as a gateway or proxy. +webscript.code.505.name=HTTP version not supported +webscript.code.505.description=Server does not support or refuses to support the HTTP protocol version that was used in the request message. + +## +## Test Web Script Server Help +## +testserver.help=alfresco/messages/webscripts-test-help.txt diff --git a/config/alfresco/templates/webscripts/org/alfresco/index.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/index.get.desc.xml index 70638bdc72..3564ad84f6 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/index.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/index.get.desc.xml @@ -1,5 +1,6 @@ Web Scripts Documentation Web Scripts Documentation + \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/status.ftl b/config/alfresco/templates/webscripts/status.ftl new file mode 100644 index 0000000000..4ddce6d97f --- /dev/null +++ b/config/alfresco/templates/webscripts/status.ftl @@ -0,0 +1,33 @@ + + + + + Web Script Status ${status.code} - ${status.codeName} + + + + + + + + +
AlfrescoWeb Script Status ${status.code} - ${status.codeName}
+
+ +
The Web Script ${url.service} has responded with a status of ${status.code} - ${status.codeName}. +
+
+ +
${status.code} Description: ${status.codeDescription} +
  +
Message:${status.message!"<Not specified>"} +
Exception:${status.exception!"None"} + <#if status.exception?exists> +
  + <#list status.exception.stackTrace as element> +
${element} + + +
+ + diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index c86956ce41..c63f637954 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -3,6 +3,19 @@ + + + + + + + + alfresco.messages.webscripts + + + + + diff --git a/source/java/org/alfresco/web/scripts/AbstractWebScript.java b/source/java/org/alfresco/web/scripts/AbstractWebScript.java index a2e1c507e6..377bea1a55 100644 --- a/source/java/org/alfresco/web/scripts/AbstractWebScript.java +++ b/source/java/org/alfresco/web/scripts/AbstractWebScript.java @@ -24,10 +24,12 @@ */ package org.alfresco.web.scripts; +import java.io.IOException; import java.io.Writer; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.alfresco.repo.jscript.Node; import org.alfresco.repo.jscript.ScriptableHashMap; @@ -39,6 +41,8 @@ import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.web.scripts.WebScriptDescription.RequiredAuthentication; import org.alfresco.web.scripts.WebScriptDescription.RequiredTransaction; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** @@ -48,12 +52,19 @@ import org.alfresco.web.scripts.WebScriptDescription.RequiredTransaction; */ public abstract class AbstractWebScript implements WebScript { + // Logger + private static final Log logger = LogFactory.getLog(AbstractWebScript.class); + // dependencies private WebScriptContext scriptContext; private WebScriptRegistry scriptRegistry; private WebScriptDescription description; private ServiceRegistry serviceRegistry; private DescriptorService descriptorService; + + // Status Template cache + private Map statusTemplates = new HashMap(); + private ReentrantReadWriteLock statusTemplateLock = new ReentrantReadWriteLock(); // @@ -102,6 +113,15 @@ public abstract class AbstractWebScript implements WebScript public void init(WebScriptRegistry scriptRegistry) { this.scriptRegistry = scriptRegistry; + this.statusTemplateLock.writeLock().lock(); + try + { + this.statusTemplates.clear(); + } + finally + { + this.statusTemplateLock.writeLock().unlock(); + } } @@ -184,12 +204,11 @@ public abstract class AbstractWebScript implements WebScript * Create a model for script usage * * @param req web script request - * @param res web script response * @param customModel custom model entries * * @return script model */ - final protected Map createScriptModel(WebScriptRequest req, WebScriptResponse res, Map customModel) + final protected Map createScriptModel(WebScriptRequest req, Map customModel) { // create script model Map model = new HashMap(7, 1.0f); @@ -301,6 +320,188 @@ public abstract class AbstractWebScript implements WebScript { getWebScriptRegistry().getTemplateProcessor().processString(template, model, writer); } + + /** + * Render an explicit response status template + * + * @param req web script request + * @param res web script response + * @param status web script status + * @param format format + * @param model model + * @throws IOException + */ + final protected void sendStatus(WebScriptRequest req, WebScriptResponse res, WebScriptStatus status, String format, Map model) + throws IOException + { + // locate status template + // NOTE: search order... + // NOTE: package path is recursed to root package + // 1) script located ...ftl + // 2) script located ..status.ftl + // 3) package located /..ftl + // 4) package located /.status.ftl + // 5) default .ftl + // 6) default status.ftl + + int statusCode = status.getCode(); + String statusFormat = (format == null) ? "" : format; + String scriptId = getDescription().getId(); + StatusTemplate template = getStatusTemplate(scriptId, statusCode, statusFormat); + + // render output + String mimetype = getWebScriptRegistry().getFormatRegistry().getMimeType(req.getAgent(), template.format); + if (mimetype == null) + { + throw new WebScriptException("Web Script format '" + template.format + "' is not registered"); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Sending status " + statusCode + " (Template: " + template.path + ")"); + logger.debug("Rendering response: content type=" + mimetype); + } + + res.reset(); + res.setStatus(statusCode); + res.setContentType(mimetype + ";charset=UTF-8"); + renderTemplate(template.path, model, res.getWriter()); + } + + /** + * Find status template + * + * Note: This method caches template search results + * + * @param scriptId + * @param statusCode + * @param format + * @return status template (or null if not found) + */ + private StatusTemplate getStatusTemplate(String scriptId, int statusCode, String format) + { + StatusTemplate statusTemplate = null; + statusTemplateLock.readLock().lock(); + + try + { + String key = statusCode + "." + format; + statusTemplate = statusTemplates.get(key); + if (statusTemplate == null) + { + // Upgrade read lock to write lock + statusTemplateLock.readLock().unlock(); + statusTemplateLock.writeLock().lock(); + + try + { + // Check again + statusTemplate = statusTemplates.get(key); + if (statusTemplate == null) + { + // Locate template in web script store + statusTemplate = getScriptStatusTemplate(scriptId, statusCode, format); + if (statusTemplate == null) + { + WebScriptPath path = getWebScriptRegistry().getPackage(Path.concatPath("/", getDescription().getScriptPath())); + statusTemplate = getPackageStatusTemplate(path, statusCode, format); + if (statusTemplate == null) + { + statusTemplate = getDefaultStatusTemplate(statusCode); + } + } + + if (logger.isDebugEnabled()) + logger.debug("Caching template " + statusTemplate.path + " for web script " + scriptId + " and status " + statusCode + " (format: " + format + ")"); + + statusTemplates.put(key, statusTemplate); + } + } + finally + { + // Downgrade lock to read + statusTemplateLock.readLock().lock(); + statusTemplateLock.writeLock().unlock(); + } + } + return statusTemplate; + } + finally + { + statusTemplateLock.readLock().unlock(); + } + } + + /** + * Find a script specific status template + * + * @param scriptId + * @param statusCode + * @param format + * @return status template (or null, if not found) + */ + private StatusTemplate getScriptStatusTemplate(String scriptId, int statusCode, String format) + { + String path = scriptId + "." + format + "." + statusCode + ".ftl"; + if (getWebScriptRegistry().getTemplateProcessor().hasTemplate(path)) + { + return new StatusTemplate(path, format); + } + path = scriptId + "." + format + ".status.ftl"; + if (getWebScriptRegistry().getTemplateProcessor().hasTemplate(path)) + { + return new StatusTemplate(path, format); + } + return null; + } + + /** + * Find a package specific status template + * + * @param scriptPath + * @param statusCode + * @param format + * @return status template (or null, if not found) + */ + private StatusTemplate getPackageStatusTemplate(WebScriptPath scriptPath, int statusCode, String format) + { + while(scriptPath != null) + { + String path = Path.concatPath(scriptPath.getPath(), format + "." + statusCode + ".ftl"); + if (getWebScriptRegistry().getTemplateProcessor().hasTemplate(path)) + { + return new StatusTemplate(path, format); + } + path = Path.concatPath(scriptPath.getPath(), format + ".status.ftl"); + if (getWebScriptRegistry().getTemplateProcessor().hasTemplate(path)) + { + return new StatusTemplate(path, format); + } + scriptPath = scriptPath.getParent(); + } + return null; + } + + /** + * Find default status template + * + * @param statusCode + * @return status template + */ + private StatusTemplate getDefaultStatusTemplate(int statusCode) + { + String path = statusCode + ".ftl"; + if (getWebScriptRegistry().getTemplateProcessor().hasTemplate(path)) + { + return new StatusTemplate(path, WebScriptResponse.HTML_FORMAT); + } + path = "status.ftl"; + if (getWebScriptRegistry().getTemplateProcessor().hasTemplate(path)) + { + return new StatusTemplate(path, WebScriptResponse.HTML_FORMAT); + } + throw new WebScriptException("Default status template /status.ftl could not be found"); + } /** * Execute a script @@ -312,4 +513,26 @@ public abstract class AbstractWebScript implements WebScript { getWebScriptRegistry().getScriptProcessor().executeScript(location, model); } + + /** + * Status Template + */ + private class StatusTemplate + { + /** + * Construct + * + * @param path + * @param format + */ + private StatusTemplate(String path, String format) + { + this.path = path; + this.format = format; + } + + private String path; + private String format; + } + } diff --git a/source/java/org/alfresco/web/scripts/DeclarativeWebScript.java b/source/java/org/alfresco/web/scripts/DeclarativeWebScript.java index f7104491a5..789946b184 100644 --- a/source/java/org/alfresco/web/scripts/DeclarativeWebScript.java +++ b/source/java/org/alfresco/web/scripts/DeclarativeWebScript.java @@ -31,11 +31,12 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import javax.servlet.http.HttpServletResponse; + import org.alfresco.repo.jscript.Node; import org.alfresco.repo.jscript.ScriptableHashMap; import org.alfresco.repo.template.TemplateNode; import org.alfresco.service.cmr.repository.ScriptLocation; -import org.alfresco.service.cmr.repository.TemplateException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.mozilla.javascript.Context; @@ -76,57 +77,90 @@ public class DeclarativeWebScript extends AbstractWebScript */ final public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException { - // construct data model for template - Map model = executeImpl(req, res); - if (model == null) - { - model = new HashMap(7, 1.0f); - } - - // execute script if it exists - if (executeScript != null) - { - if (logger.isDebugEnabled()) - logger.debug("Executing script " + executeScript); - - Map scriptModel = createScriptModel(req, res, model); - // add return model allowing script to add items to template model - Map returnModel = new ScriptableHashMap(); - scriptModel.put("model", returnModel); - executeScript(executeScript, scriptModel); - mergeScriptModelIntoTemplateModel(returnModel, model); - } - - // process requested format + // retrieve requested format String format = req.getFormat(); if (format == null || format.length() == 0) { format = getDescription().getDefaultFormat(); } - - String mimetype = getWebScriptRegistry().getFormatRegistry().getMimeType(req.getAgent(), format); - if (mimetype == null) - { - throw new WebScriptException("Web Script format '" + format + "' is not registered"); - } - // render output - res.setContentType(mimetype + ";charset=UTF-8"); - - if (logger.isDebugEnabled()) - logger.debug("Response content type: " + mimetype); - try { + // establish mimetype from format + String mimetype = getWebScriptRegistry().getFormatRegistry().getMimeType(req.getAgent(), format); + if (mimetype == null) + { + throw new WebScriptException("Web Script format '" + format + "' is not registered"); + } + + // construct data model for template + WebScriptStatus status = new WebScriptStatus(); + Map model = executeImpl(req, status); + if (model == null) + { + model = new HashMap(7, 1.0f); + } + model.put("status", status); + + // execute script if it exists + if (executeScript != null) + { + if (logger.isDebugEnabled()) + logger.debug("Executing script " + executeScript); + + Map scriptModel = createScriptModel(req, model); + // add return model allowing script to add items to template model + Map returnModel = new ScriptableHashMap(); + scriptModel.put("model", returnModel); + executeScript(executeScript, scriptModel); + mergeScriptModelIntoTemplateModel(returnModel, model); + } + + // create model for template rendering Map templateModel = createTemplateModel(req, res, model); - renderFormatTemplate(format, templateModel, res.getWriter()); + + // is a redirect to a status specific template required? + if (status.getRedirect()) + { + sendStatus(req, res, status, format, templateModel); + } + else + { + // render output + int statusCode = status.getCode(); + if (statusCode != HttpServletResponse.SC_OK) + { + res.setStatus(statusCode); + } + res.setContentType(mimetype + ";charset=UTF-8"); + + if (logger.isDebugEnabled()) + logger.debug("Rendering response: content type=" + mimetype + ", status=" + statusCode); + + renderFormatTemplate(format, templateModel, res.getWriter()); + } } - catch(TemplateException e) + catch(Throwable e) { - throw new WebScriptException("Failed to process format '" + format + "'", e); + // extract status code, if specified + int statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + if (e instanceof WebScriptException) + { + statusCode = ((WebScriptException)e).getStatus(); + } + + // send status + WebScriptStatus status = new WebScriptStatus(); + status.setCode(statusCode); + status.setMessage(e.getMessage()); + status.setException(e); + Map customModel = new HashMap(); + customModel.put("status", status); + Map templateModel = createTemplateModel(req, res, customModel); + sendStatus(req, res, status, format, templateModel); } } - + /** * Merge script generated model into template-ready model * @@ -192,10 +226,9 @@ public class DeclarativeWebScript extends AbstractWebScript * Execute custom Java logic * * @param req Web Script request - * @param res Web Script response * @return custom service model */ - protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) { return null; } diff --git a/source/java/org/alfresco/web/scripts/DeclarativeWebScriptRegistry.java b/source/java/org/alfresco/web/scripts/DeclarativeWebScriptRegistry.java index 6a6c0ecafe..afe4ec2cc9 100644 --- a/source/java/org/alfresco/web/scripts/DeclarativeWebScriptRegistry.java +++ b/source/java/org/alfresco/web/scripts/DeclarativeWebScriptRegistry.java @@ -81,7 +81,7 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean // map of web scripts by url // NOTE: The map is sorted by url (descending order) - private Map webscriptsByURL = new TreeMap(Collections.reverseOrder()); + private Map webscriptsByURL = new TreeMap(Collections.reverseOrder()); // map of web script packages by path private Map packageByPath = new TreeMap(); @@ -258,6 +258,7 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean for (URI uri : serviceDesc.getURIs()) { // establish static part of url template + boolean wildcard = false; String uriTemplate = uri.getURI(); int queryArgIdx = uriTemplate.indexOf('?'); if (queryArgIdx != -1) @@ -268,6 +269,7 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean if (tokenIdx != -1) { uriTemplate = uriTemplate.substring(0, tokenIdx); + wildcard = true; } if (serviceDesc.getFormatStyle() != WebScriptDescription.FormatStyle.argument) { @@ -282,7 +284,8 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean String uriIdx = serviceDesc.getMethod().toString() + ":" + uriTemplate; if (webscriptsByURL.containsKey(uriIdx)) { - WebScript existingService = webscriptsByURL.get(uriIdx); + URLIndex urlIndex = webscriptsByURL.get(uriIdx); + WebScript existingService = urlIndex.script; if (!existingService.getDescription().getId().equals(serviceDesc.getId())) { String msg = "Web Script document " + serviceDesc.getDescPath() + " is attempting to define the url '" + uriIdx + "' already defined by " + existingService.getDescription().getDescPath(); @@ -291,7 +294,8 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean } else { - webscriptsByURL.put(uriIdx, serviceImpl); + URLIndex urlIndex = new URLIndex(uriTemplate, wildcard, serviceImpl); + webscriptsByURL.put(uriIdx, urlIndex); if (logger.isDebugEnabled()) logger.debug("Registered Web Script URL '" + uriIdx + "'"); @@ -357,7 +361,7 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean subpath = path.createChildPath(part); uriByPath.put(subpath.getPath(), subpath); if (logger.isDebugEnabled()) - logger.debug("Registered Web Script URI " + subpath.getPath()); + logger.debug("Registered Web Script URI Path " + subpath.getPath()); } path = subpath; } @@ -387,7 +391,8 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean } // retrieve script path - String scriptPath = serviceDescPath.substring(0, serviceDescPath.lastIndexOf('/')); + int iPathIdx = serviceDescPath.lastIndexOf('/'); + String scriptPath = serviceDescPath.substring(0, iPathIdx == -1 ? 0 : iPathIdx); // retrieve script id String id = serviceDescPath.substring(0, serviceDescPath.lastIndexOf(".desc.xml")); @@ -569,19 +574,38 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean */ public WebScriptMatch findWebScript(String method, String uri) { + long startTime = System.currentTimeMillis(); + // TODO: Replace with more efficient approach + String matchedPath = null; DeclarativeWebScriptMatch apiServiceMatch = null; String match = method.toString().toUpperCase() + ":" + uri; - for (Map.Entry service : webscriptsByURL.entrySet()) + + // locate full match - on URI and METHOD + for (Map.Entry entry : webscriptsByURL.entrySet()) { - String indexedPath = service.getKey(); - if (match.startsWith(indexedPath)) + URLIndex urlIndex = entry.getValue(); + String index = entry.getKey(); + if ((urlIndex.wildcardPath && match.startsWith(index)) || (!urlIndex.wildcardPath && match.equals(index))) { - String matchPath = indexedPath.substring(indexedPath.indexOf(':') +1); - apiServiceMatch = new DeclarativeWebScriptMatch(matchPath, service.getValue()); + apiServiceMatch = new DeclarativeWebScriptMatch(urlIndex.path, urlIndex.script); break; } + else if ((urlIndex.wildcardPath && uri.startsWith(urlIndex.path)) || (!urlIndex.wildcardPath && uri.equals(urlIndex.path))) + { + matchedPath = urlIndex.path; + } } + + // locate URI match + if (apiServiceMatch == null && matchedPath != null) + { + apiServiceMatch = new DeclarativeWebScriptMatch(matchedPath); + } + + if (logger.isDebugEnabled()) + logger.debug("Web Script index lookup for uri " + uri + " took " + (System.currentTimeMillis() - startTime) + "ms"); + return apiServiceMatch; } @@ -634,6 +658,7 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean { private String path; private WebScript service; + private Kind kind; /** * Construct @@ -643,9 +668,30 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean */ public DeclarativeWebScriptMatch(String path, WebScript service) { + this.kind = Kind.FULL; this.path = path; this.service = service; } + + /** + * Construct + * + * @param path + * @param service + */ + public DeclarativeWebScriptMatch(String path) + { + this.kind = Kind.URI; + this.path = path; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptMatch#getKind() + */ + public Kind getKind() + { + return this.kind; + } /* (non-Javadoc) * @see org.alfresco.web.scripts.WebScriptMatch#getPath() @@ -664,4 +710,21 @@ public class DeclarativeWebScriptRegistry extends AbstractLifecycleBean } } + /** + * Web Script URL Index Entry + */ + private static class URLIndex + { + private URLIndex(String path, boolean wildcardPath, WebScript script) + { + this.path = path; + this.wildcardPath = wildcardPath; + this.script = script; + } + + private String path; + private boolean wildcardPath; + private WebScript script; + } + } diff --git a/source/java/org/alfresco/web/scripts/TemplateProcessor.java b/source/java/org/alfresco/web/scripts/TemplateProcessor.java index 0550da5f5e..beb41a7729 100644 --- a/source/java/org/alfresco/web/scripts/TemplateProcessor.java +++ b/source/java/org/alfresco/web/scripts/TemplateProcessor.java @@ -24,6 +24,8 @@ */ package org.alfresco.web.scripts; +import java.io.IOException; + import org.alfresco.repo.template.FreeMarkerProcessor; import org.alfresco.repo.template.QNameAwareObjectWrapper; import org.alfresco.service.cmr.repository.ProcessorExtension; @@ -37,6 +39,7 @@ import org.springframework.context.ApplicationListener; import freemarker.cache.MruCacheStorage; import freemarker.cache.TemplateLoader; import freemarker.template.Configuration; +import freemarker.template.Template; import freemarker.template.TemplateExceptionHandler; @@ -106,6 +109,26 @@ public class TemplateProcessor extends FreeMarkerProcessor implements Applicatio } } + /** + * Determines if a template exists + * + * @param template + * @return true => exists + */ + public boolean hasTemplate(String templatePath) + { + boolean hasTemplate = false; + try + { + Template template = templateConfig.getTemplate(templatePath); + hasTemplate = (template != null); + } + catch(IOException e) + { + } + return hasTemplate; + } + /** * Initialise FreeMarker Configuration */ diff --git a/source/java/org/alfresco/web/scripts/TestWebScriptServer.java b/source/java/org/alfresco/web/scripts/TestWebScriptServer.java index 77a5da20c5..4ec42f4135 100644 --- a/source/java/org/alfresco/web/scripts/TestWebScriptServer.java +++ b/source/java/org/alfresco/web/scripts/TestWebScriptServer.java @@ -353,12 +353,7 @@ public class TestWebScriptServer command[0].equals("post") || command[0].equals("delete")) { - if (command.length < 2) - { - return "Syntax Error.\n"; - } - - String uri = command[1]; + String uri = (command.length > 1) ? command[1] : null; MockHttpServletResponse res = submitRequest(command[0], uri); bout.write(res.getContentAsByteArray()); out.println(); @@ -392,23 +387,24 @@ public class TestWebScriptServer { MockHttpServletRequest req = new MockHttpServletRequest(method, uri); - // set parameters - int iArgIndex = uri.indexOf('?'); - if (iArgIndex != -1 && iArgIndex != uri.length() -1) - { - String uriArgs = uri.substring(iArgIndex +1); - String[] args = uriArgs.split("&"); - for (String arg : args) - { - String[] parts = arg.split("="); - req.addParameter(parts[0], (parts.length == 2) ? parts[1] : null); - } - } - - // set paths req.setContextPath("/alfresco"); req.setServletPath("/service"); - req.setPathInfo(iArgIndex == -1 ? uri : uri.substring(0, iArgIndex)); + + if (uri != null) + { + int iArgIndex = uri.indexOf('?'); + if (iArgIndex != -1 && iArgIndex != uri.length() -1) + { + String uriArgs = uri.substring(iArgIndex +1); + String[] args = uriArgs.split("&"); + for (String arg : args) + { + String[] parts = arg.split("="); + req.addParameter(parts[0], (parts.length == 2) ? parts[1] : null); + } + } + req.setPathInfo(iArgIndex == -1 ? uri : uri.substring(0, iArgIndex)); + } return req; } diff --git a/source/java/org/alfresco/web/scripts/WebScriptException.java b/source/java/org/alfresco/web/scripts/WebScriptException.java index f7c336e226..bc7ec77c6e 100644 --- a/source/java/org/alfresco/web/scripts/WebScriptException.java +++ b/source/java/org/alfresco/web/scripts/WebScriptException.java @@ -24,6 +24,8 @@ */ package org.alfresco.web.scripts; +import javax.servlet.http.HttpServletResponse; + import org.alfresco.error.AlfrescoRuntimeException; /** @@ -35,6 +37,15 @@ public class WebScriptException extends AlfrescoRuntimeException { private static final long serialVersionUID = -7338963365877285084L; + private int status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + + + public WebScriptException(int status, String msgId) + { + this(msgId); + this.status = status; + } + public WebScriptException(String msgId) { super(msgId); @@ -54,4 +65,10 @@ public class WebScriptException extends AlfrescoRuntimeException { super(msgId, args, cause); } + + public int getStatus() + { + return status; + } + } diff --git a/source/java/org/alfresco/web/scripts/WebScriptMatch.java b/source/java/org/alfresco/web/scripts/WebScriptMatch.java index 325368f568..90cc97e12b 100644 --- a/source/java/org/alfresco/web/scripts/WebScriptMatch.java +++ b/source/java/org/alfresco/web/scripts/WebScriptMatch.java @@ -26,13 +26,25 @@ package org.alfresco.web.scripts; /** - * Represents a URL to Web Script match + * Represents a URL request to Web Script match * * @author davidc */ public interface WebScriptMatch { - + public enum Kind + { + /** URL request matches on URI only */ + URI, + /** URL request matches on URI and Method */ + FULL + }; + + /** + * Gets the kind of Match + */ + public Kind getKind(); + /** * Gets the part of the request URL that matched the Web Script URL Template * @@ -43,7 +55,7 @@ public interface WebScriptMatch /** * Gets the matching web script * - * @return service + * @return service (or null, if match kind is URI) */ public WebScript getWebScript(); diff --git a/source/java/org/alfresco/web/scripts/WebScriptRequestImpl.java b/source/java/org/alfresco/web/scripts/WebScriptRequestImpl.java index 420e0fd399..c5c724a4ba 100644 --- a/source/java/org/alfresco/web/scripts/WebScriptRequestImpl.java +++ b/source/java/org/alfresco/web/scripts/WebScriptRequestImpl.java @@ -40,13 +40,21 @@ public abstract class WebScriptRequestImpl implements WebScriptRequest */ public String getExtensionPath() { - String servicePath = getServiceMatch().getPath(); - String extensionPath = getPathInfo(); - int extIdx = extensionPath.indexOf(servicePath); - if (extIdx != -1) + String extensionPath = ""; + WebScriptMatch match = getServiceMatch(); + if (match != null) { - int extLength = (servicePath.endsWith("/") ? servicePath.length() : servicePath.length() + 1); - extensionPath = extensionPath.substring(extIdx + extLength); + String servicePath = getServiceMatch().getPath(); + extensionPath = getPathInfo(); + if (extensionPath != null) + { + int extIdx = extensionPath.indexOf(servicePath); + if (extIdx != -1) + { + int extLength = (servicePath.endsWith("/") ? servicePath.length() : servicePath.length() + 1); + extensionPath = extensionPath.substring(extIdx + extLength); + } + } } return extensionPath; } @@ -65,30 +73,37 @@ public abstract class WebScriptRequestImpl implements WebScriptRequest public String getFormat() { String format = null; - FormatStyle style = getServiceMatch().getWebScript().getDescription().getFormatStyle(); - - // extract format from extension - if (style == FormatStyle.extension || style == FormatStyle.any) + WebScriptMatch match = getServiceMatch(); + if (match != null) { - String pathInfo = getPathInfo(); - int extIdx = pathInfo.lastIndexOf('.'); - if (extIdx != -1) + FormatStyle style = getServiceMatch().getWebScript().getDescription().getFormatStyle(); + + // extract format from extension + if (style == FormatStyle.extension || style == FormatStyle.any) { - format = pathInfo.substring(extIdx +1); - } - } - - // extract format from argument - if (style == FormatStyle.argument || style == FormatStyle.any) - { - String argFormat = getParameter("format"); - if (argFormat != null) - { - if (format != null && format.length() > 0) + String pathInfo = getPathInfo(); + if (pathInfo != null) { - throw new WebScriptException("Format specified both in extension and format argument"); + int extIdx = pathInfo.lastIndexOf('.'); + if (extIdx != -1) + { + format = pathInfo.substring(extIdx +1); + } + } + } + + // extract format from argument + if (style == FormatStyle.argument || style == FormatStyle.any) + { + String argFormat = getParameter("format"); + if (argFormat != null) + { + if (format != null && format.length() > 0) + { + throw new WebScriptException("Format specified both in extension and format argument"); + } + format = argFormat; } - format = argFormat; } } @@ -100,7 +115,12 @@ public abstract class WebScriptRequestImpl implements WebScriptRequest */ public FormatStyle getFormatStyle() { - FormatStyle style = getServiceMatch().getWebScript().getDescription().getFormatStyle(); + WebScriptMatch match = getServiceMatch(); + if (match == null) + { + return FormatStyle.any; + } + FormatStyle style = match.getWebScript().getDescription().getFormatStyle(); if (style != FormatStyle.any) { return style; diff --git a/source/java/org/alfresco/web/scripts/WebScriptResponse.java b/source/java/org/alfresco/web/scripts/WebScriptResponse.java index 3dde185821..7b6aef5017 100644 --- a/source/java/org/alfresco/web/scripts/WebScriptResponse.java +++ b/source/java/org/alfresco/web/scripts/WebScriptResponse.java @@ -44,6 +44,12 @@ public interface WebScriptResponse public static final String JSON_FORMAT = "json"; public static final String OPENSEARCH_DESCRIPTION_FORMAT = "opensearchdescription"; + /** + * Sets the Response Status + * + * @param status + */ + public void setStatus(int status); /** * Sets the Content Type @@ -68,6 +74,11 @@ public interface WebScriptResponse */ public OutputStream getOutputStream() throws IOException; + /** + * Clears response buffer + */ + public void reset(); + /** * Encode a script URL * diff --git a/source/java/org/alfresco/web/scripts/WebScriptRuntime.java b/source/java/org/alfresco/web/scripts/WebScriptRuntime.java index 4ac9ef8666..1c969ff752 100644 --- a/source/java/org/alfresco/web/scripts/WebScriptRuntime.java +++ b/source/java/org/alfresco/web/scripts/WebScriptRuntime.java @@ -25,8 +25,14 @@ package org.alfresco.web.scripts; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.service.transaction.TransactionService; @@ -77,94 +83,159 @@ public abstract class WebScriptRuntime long startRuntime = System.currentTimeMillis(); String method = getScriptMethod(); - String scriptUrl = getScriptUrl(); - + String scriptUrl = null; + try { + // extract script url + scriptUrl = getScriptUrl(); + if (scriptUrl == null || scriptUrl.length() == 0) + { + throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Script URL not specified"); + } + if (logger.isDebugEnabled()) logger.debug("Processing script url (" + method + ") " + scriptUrl); WebScriptMatch match = registry.findWebScript(method, scriptUrl); - if (match != null) + if (match == null || match.getKind() == WebScriptMatch.Kind.URI) { - // setup web script context - final WebScriptRequest scriptReq = createRequest(match); - final WebScriptResponse scriptRes = createResponse(); - - if (logger.isDebugEnabled()) - logger.debug("Agent: " + scriptReq.getAgent()); - - long startScript = System.currentTimeMillis(); - final WebScript script = match.getWebScript(); - final WebScriptDescription description = script.getDescription(); - - try + if (match == null) { + String msg = "Script url " + scriptUrl + " does not map to a Web Script."; if (logger.isDebugEnabled()) + logger.debug(msg); + throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, msg); + } + else + { + String msg = "Script url " + scriptUrl + " does not support the method " + method; + if (logger.isDebugEnabled()) + logger.debug(msg); + throw new WebScriptException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); + } + } + + // create web script request & response + final WebScriptRequest scriptReq = createRequest(match); + final WebScriptResponse scriptRes = createResponse(); + + if (logger.isDebugEnabled()) + logger.debug("Agent: " + scriptReq.getAgent()); + + long startScript = System.currentTimeMillis(); + final WebScript script = match.getWebScript(); + final WebScriptDescription description = script.getDescription(); + + try + { + if (logger.isDebugEnabled()) + { + String user = AuthenticationUtil.getCurrentUserName(); + String locale = I18NUtil.getLocale().toString(); + String reqFormat = scriptReq.getFormat(); + String format = (reqFormat == null || reqFormat.length() == 0) ? "default" : scriptReq.getFormat(); + WebScriptDescription desc = scriptReq.getServiceMatch().getWebScript().getDescription(); + logger.debug("Format style: " + desc.getFormatStyle()); + logger.debug("Default format: " + desc.getDefaultFormat()); + logger.debug("Invoking Web Script " + description.getId() + (user == null ? " (unauthenticated)" : " (authenticated as " + user + ") (format " + format + ") (" + locale + ")")); + } + + if (description.getRequiredTransaction() == RequiredTransaction.none) + { + authenticatedExecute(scriptReq, scriptRes); + } + else + { + // encapsulate script within transaction + TransactionUtil.TransactionWork work = new TransactionUtil.TransactionWork() { - String user = AuthenticationUtil.getCurrentUserName(); - String locale = I18NUtil.getLocale().toString(); - String reqFormat = scriptReq.getFormat(); - String format = (reqFormat == null || reqFormat.length() == 0) ? "default" : scriptReq.getFormat(); - WebScriptDescription desc = scriptReq.getServiceMatch().getWebScript().getDescription(); - logger.debug("Format style: " + desc.getFormatStyle()); - logger.debug("Default format: " + desc.getDefaultFormat()); - logger.debug("Invoking Web Script " + description.getId() + (user == null ? " (unauthenticated)" : " (authenticated as " + user + ") (format " + format + ") (" + locale + ")")); - } - - if (description.getRequiredTransaction() == RequiredTransaction.none) + public Object doWork() throws Throwable + { + if (logger.isDebugEnabled()) + logger.debug("Begin transaction: " + description.getRequiredTransaction()); + + authenticatedExecute(scriptReq, scriptRes); + + if (logger.isDebugEnabled()) + logger.debug("End transaction: " + description.getRequiredTransaction()); + + return null; + } + }; + + if (description.getRequiredTransaction() == RequiredTransaction.required) { - authenticatedExecute(scriptReq, scriptRes); + TransactionUtil.executeInUserTransaction(transactionService, work); } else { - // encapsulate script within transaction - TransactionUtil.TransactionWork work = new TransactionUtil.TransactionWork() - { - public Object doWork() throws Throwable - { - if (logger.isDebugEnabled()) - logger.debug("Begin transaction: " + description.getRequiredTransaction()); - - authenticatedExecute(scriptReq, scriptRes); - - if (logger.isDebugEnabled()) - logger.debug("End transaction: " + description.getRequiredTransaction()); - - return null; - } - }; - - if (description.getRequiredTransaction() == RequiredTransaction.required) - { - TransactionUtil.executeInUserTransaction(transactionService, work); - } - else - { - TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, work); - } - } - } - catch(IOException e) - { - throw new WebScriptException("Failed to execute script", e); - } - finally - { - if (logger.isDebugEnabled()) - { - long endScript = System.currentTimeMillis(); - logger.debug("Web Script " + description.getId() + " executed in " + (endScript - startScript) + "ms"); + TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, work); } } } - else + finally { - String msg = "Script url (" + method + ") " + scriptUrl + " does not map to a Web Script."; if (logger.isDebugEnabled()) - logger.debug(msg); + { + long endScript = System.currentTimeMillis(); + logger.debug("Web Script " + description.getId() + " executed in " + (endScript - startScript) + "ms"); + } + } + } + catch(Throwable e) + { + // extract status code, if specified + int statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + if (e instanceof WebScriptException) + { + statusCode = ((WebScriptException)e).getStatus(); + } - throw new WebScriptException(msg); + // create web script status for status template rendering + WebScriptStatus status = new WebScriptStatus(); + status.setCode(statusCode); + status.setMessage(e.getMessage()); + status.setException(e); + + // create basic model for status template rendering + WebScriptRequest req = createRequest(null); + WebScriptResponse res = createResponse(); + Map model = new HashMap(); + model.put("status", status); + model.put("url", new URLModel(req)); + + // locate status template + // NOTE: search order... + // 1) root located .ftl + // 2) root located status.ftl + String templatePath = "/" + statusCode + ".ftl"; + if (!registry.getTemplateProcessor().hasTemplate(templatePath)) + { + templatePath = "/status.ftl"; + if (!registry.getTemplateProcessor().hasTemplate(templatePath)) + { + throw new WebScriptException("Failed to find status template " + status + " (format: " + WebScriptResponse.HTML_FORMAT + ")"); + } + } + + // render output + if (logger.isDebugEnabled()) + { + logger.debug("Sending status " + statusCode + " (Template: " + templatePath + ")"); + logger.debug("Rendering response: content type=" + MimetypeMap.MIMETYPE_HTML); + } + + res.reset(); + res.setStatus(statusCode); + res.setContentType(MimetypeMap.MIMETYPE_HTML + ";charset=UTF-8"); + try + { + registry.getTemplateProcessor().process(templatePath, model, res.getWriter()); + } + catch (IOException e1) + { + throw new WebScriptException("Internal error", e1); } } finally @@ -196,7 +267,7 @@ public abstract class WebScriptRuntime } else if (required == RequiredAuthentication.user && isGuest) { - throw new WebScriptException("Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access."); + throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access."); } else { @@ -220,7 +291,6 @@ public abstract class WebScriptRuntime // if (authenticate(required, isGuest)) { - // // Execute Web Script wrappedExecute(scriptReq, scriptRes); } diff --git a/source/java/org/alfresco/web/scripts/WebScriptServletRequest.java b/source/java/org/alfresco/web/scripts/WebScriptServletRequest.java index fd08ad3b2c..b9155e50c5 100644 --- a/source/java/org/alfresco/web/scripts/WebScriptServletRequest.java +++ b/source/java/org/alfresco/web/scripts/WebScriptServletRequest.java @@ -120,7 +120,8 @@ public class WebScriptServletRequest extends WebScriptRequestImpl */ public String getServicePath() { - return getServiceContextPath() + req.getPathInfo(); + String pathInfo = req.getPathInfo(); + return getServiceContextPath() + ((pathInfo == null) ? "" : req.getPathInfo()); } /* (non-Javadoc) diff --git a/source/java/org/alfresco/web/scripts/WebScriptServletResponse.java b/source/java/org/alfresco/web/scripts/WebScriptServletResponse.java index c0469dfdeb..dc622abfac 100644 --- a/source/java/org/alfresco/web/scripts/WebScriptServletResponse.java +++ b/source/java/org/alfresco/web/scripts/WebScriptServletResponse.java @@ -28,7 +28,6 @@ import java.io.IOException; import java.io.OutputStream; import java.io.Writer; -import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** @@ -60,6 +59,14 @@ public class WebScriptServletResponse implements WebScriptResponse return res; } + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptResponse#setStatus(int) + */ + public void setStatus(int status) + { + res.setStatus(status); + } + /* (non-Javadoc) * @see org.alfresco.web.scripts.WebScriptResponse#setContentType(java.lang.String) */ @@ -68,6 +75,20 @@ public class WebScriptServletResponse implements WebScriptResponse res.setContentType(contentType); } + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptResponse#reset() + */ + public void reset() + { + try + { + res.reset(); + } + catch(IllegalStateException e) + { + } + } + /* (non-Javadoc) * @see org.alfresco.web.scripts.WebScriptResponse#getWriter() */ @@ -91,5 +112,5 @@ public class WebScriptServletResponse implements WebScriptResponse { return url; } - + } diff --git a/source/java/org/alfresco/web/scripts/WebScriptStatus.java b/source/java/org/alfresco/web/scripts/WebScriptStatus.java new file mode 100644 index 0000000000..87f91b5847 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptStatus.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.i18n.I18NUtil; + + +/** + * Web Script Status + * + * Records the outcome of a Web Script. + * + * @author davidc + */ +public class WebScriptStatus +{ + private Throwable exception = null; + private int code = HttpServletResponse.SC_OK; + private String message = null; + private boolean redirect = false; + + + /** + * @param exception + */ + public void setException(Throwable exception) + { + this.exception = exception; + } + + /** + * @return exception + */ + public Throwable getException() + { + return exception; + } + + public Throwable jsGet_exception() + { + return getException(); + } + + /** + * @param message + */ + public void setMessage(String message) + { + this.message = message; + } + + public void jsSet_message(String message) + { + setMessage(message); + } + + /** + * @return message + */ + public String getMessage() + { + return message; + } + + public String jsGet_message() + { + return getMessage(); + } + + /** + * @param redirect redirect to status code response + */ + public void setRedirect(boolean redirect) + { + this.redirect = redirect; + } + + public void jsSet_redirect(boolean redirect) + { + setRedirect(redirect); + } + + /** + * @return redirect to status code response + */ + public boolean getRedirect() + { + return redirect; + } + + public boolean jsGet_redirect() + { + return getRedirect(); + } + + /** + * @see javax.servlet.http.HTTPServletResponse + * + * @param code status code + */ + public void setCode(int code) + { + this.code = code; + } + + public void jsSet_code(int code) + { + this.code = code; + } + + /** + * @return status code + */ + public int getCode() + { + return code; + } + + public int jsGet_code() + { + return getCode(); + } + + /** + * Gets the short name of the status code + * + * @return status code name + */ + public String getCodeName() + { + return I18NUtil.getMessage("webscript.code." + code + ".name"); + } + + public String jsGet_codeName() + { + return getCodeName(); + } + + /** + * Gets the description of the status code + * + * @return status code description + */ + public String getCodeDescription() + { + return I18NUtil.getMessage("webscript.code." + code + ".description"); + } + + public String jsGet_codeDescription() + { + return getCodeDescription(); + } + +} diff --git a/source/java/org/alfresco/web/scripts/bean/Index.java b/source/java/org/alfresco/web/scripts/bean/Index.java index 1b163aa6d2..5a12684b68 100644 --- a/source/java/org/alfresco/web/scripts/bean/Index.java +++ b/source/java/org/alfresco/web/scripts/bean/Index.java @@ -29,7 +29,7 @@ import java.util.Map; import org.alfresco.web.scripts.DeclarativeWebScript; import org.alfresco.web.scripts.WebScriptRequest; -import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.WebScriptStatus; /** @@ -44,7 +44,7 @@ public class Index extends DeclarativeWebScript * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */ @Override - protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) { Map model = new HashMap(7, 1.0f); model.put("webscripts", getWebScriptRegistry().getWebScripts()); diff --git a/source/java/org/alfresco/web/scripts/bean/IndexPackage.java b/source/java/org/alfresco/web/scripts/bean/IndexPackage.java index 1f39c380a5..09a0c54f42 100644 --- a/source/java/org/alfresco/web/scripts/bean/IndexPackage.java +++ b/source/java/org/alfresco/web/scripts/bean/IndexPackage.java @@ -31,7 +31,7 @@ import org.alfresco.web.scripts.DeclarativeWebScript; import org.alfresco.web.scripts.WebScriptException; import org.alfresco.web.scripts.WebScriptPath; import org.alfresco.web.scripts.WebScriptRequest; -import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.WebScriptStatus; /** @@ -46,7 +46,7 @@ public class IndexPackage extends DeclarativeWebScript * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */ @Override - protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) { // extract web script package String packagePath = req.getExtensionPath(); diff --git a/source/java/org/alfresco/web/scripts/bean/IndexURI.java b/source/java/org/alfresco/web/scripts/bean/IndexURI.java index f5e360d848..4a57f9593b 100644 --- a/source/java/org/alfresco/web/scripts/bean/IndexURI.java +++ b/source/java/org/alfresco/web/scripts/bean/IndexURI.java @@ -31,7 +31,7 @@ import org.alfresco.web.scripts.DeclarativeWebScript; import org.alfresco.web.scripts.WebScriptException; import org.alfresco.web.scripts.WebScriptPath; import org.alfresco.web.scripts.WebScriptRequest; -import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.WebScriptStatus; /** @@ -46,7 +46,7 @@ public class IndexURI extends DeclarativeWebScript * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */ @Override - protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) { // extract web script package String uriPath = req.getExtensionPath(); diff --git a/source/java/org/alfresco/web/scripts/bean/IndexUpdate.java b/source/java/org/alfresco/web/scripts/bean/IndexUpdate.java index f60c70545f..407edbf0c0 100644 --- a/source/java/org/alfresco/web/scripts/bean/IndexUpdate.java +++ b/source/java/org/alfresco/web/scripts/bean/IndexUpdate.java @@ -31,7 +31,7 @@ import java.util.Map; import org.alfresco.web.scripts.DeclarativeWebScript; import org.alfresco.web.scripts.WebScriptRequest; -import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.WebScriptStatus; /** @@ -46,7 +46,7 @@ public class IndexUpdate extends DeclarativeWebScript * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */ @Override - protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) { List tasks = new ArrayList(); diff --git a/source/java/org/alfresco/web/scripts/bean/KeywordSearch.java b/source/java/org/alfresco/web/scripts/bean/KeywordSearch.java index 16935ffa0b..bc9bf09169 100644 --- a/source/java/org/alfresco/web/scripts/bean/KeywordSearch.java +++ b/source/java/org/alfresco/web/scripts/bean/KeywordSearch.java @@ -42,7 +42,7 @@ import org.alfresco.util.ParameterCheck; import org.alfresco.web.scripts.DeclarativeWebScript; import org.alfresco.web.scripts.WebScriptException; import org.alfresco.web.scripts.WebScriptRequest; -import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.WebScriptStatus; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -78,7 +78,7 @@ public class KeywordSearch extends DeclarativeWebScript * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */ @Override - protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) { // // process arguments diff --git a/source/java/org/alfresco/web/scripts/bean/Login.java b/source/java/org/alfresco/web/scripts/bean/Login.java index ddcb9d54ce..83ba1f60db 100644 --- a/source/java/org/alfresco/web/scripts/bean/Login.java +++ b/source/java/org/alfresco/web/scripts/bean/Login.java @@ -27,11 +27,14 @@ package org.alfresco.web.scripts.bean; import java.util.HashMap; import java.util.Map; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.web.scripts.DeclarativeWebScript; import org.alfresco.web.scripts.WebScriptException; import org.alfresco.web.scripts.WebScriptRequest; -import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.WebScriptStatus; /** @@ -57,26 +60,34 @@ public class Login extends DeclarativeWebScript * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */ @Override - protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) { // extract username and password String username = req.getParameter("u"); if (username == null || username.length() == 0) { - throw new WebScriptException("Username has not been specified"); + throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Username not specified"); } String password = req.getParameter("pw"); - - // get ticket - authenticationService.authenticate(username, password == null ? null : password.toCharArray()); - - // add ticket to model for javascript and template access + if (password == null) + { + throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Password not specified"); + } + try { + // get ticket + authenticationService.authenticate(username, password.toCharArray()); + + // add ticket to model for javascript and template access Map model = new HashMap(7, 1.0f); model.put("ticket", authenticationService.getCurrentTicket()); return model; } + catch(AuthenticationException e) + { + throw new WebScriptException(HttpServletResponse.SC_FORBIDDEN, "Login failed"); + } finally { authenticationService.clearCurrentSecurityContext(); diff --git a/source/java/org/alfresco/web/scripts/bean/LoginTicket.java b/source/java/org/alfresco/web/scripts/bean/LoginTicket.java index 1515853d8b..ba42d372d3 100644 --- a/source/java/org/alfresco/web/scripts/bean/LoginTicket.java +++ b/source/java/org/alfresco/web/scripts/bean/LoginTicket.java @@ -27,13 +27,15 @@ package org.alfresco.web.scripts.bean; import java.util.HashMap; import java.util.Map; +import javax.servlet.http.HttpServletResponse; + import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.TicketComponent; import org.alfresco.web.scripts.DeclarativeWebScript; import org.alfresco.web.scripts.WebScriptException; import org.alfresco.web.scripts.WebScriptRequest; -import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.WebScriptStatus; /** @@ -59,15 +61,19 @@ public class LoginTicket extends DeclarativeWebScript * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */ @Override - protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) { // retrieve ticket from request and current ticket String ticket = req.getExtensionPath(); if (ticket == null && ticket.length() == 0) { - throw new WebScriptException("Ticket not specified"); + throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Ticket not specified"); } + // construct model for ticket + Map model = new HashMap(7, 1.0f); + model.put("ticket", ticket); + try { String ticketUser = ticketComponent.validateTicket(ticket); @@ -75,17 +81,18 @@ public class LoginTicket extends DeclarativeWebScript // do not go any further if tickets are different if (!AuthenticationUtil.getCurrentUserName().equals(ticketUser)) { - // TODO: 404 error - throw new WebScriptException("Ticket not found"); + status.setRedirect(true); + status.setCode(HttpServletResponse.SC_NOT_FOUND); + status.setMessage("Ticket not found"); } } catch(AuthenticationException e) { - throw new WebScriptException("Ticket not found"); + status.setRedirect(true); + status.setCode(HttpServletResponse.SC_NOT_FOUND); + status.setMessage("Ticket not found"); } - Map model = new HashMap(7, 1.0f); - model.put("ticket", ticket); return model; } diff --git a/source/java/org/alfresco/web/scripts/bean/LoginTicketDelete.java b/source/java/org/alfresco/web/scripts/bean/LoginTicketDelete.java index 898e5f335e..f65b77093c 100644 --- a/source/java/org/alfresco/web/scripts/bean/LoginTicketDelete.java +++ b/source/java/org/alfresco/web/scripts/bean/LoginTicketDelete.java @@ -27,6 +27,8 @@ package org.alfresco.web.scripts.bean; import java.util.HashMap; import java.util.Map; +import javax.servlet.http.HttpServletResponse; + import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.TicketComponent; @@ -34,7 +36,7 @@ import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.web.scripts.DeclarativeWebScript; import org.alfresco.web.scripts.WebScriptException; import org.alfresco.web.scripts.WebScriptRequest; -import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.WebScriptStatus; /** @@ -69,15 +71,19 @@ public class LoginTicketDelete extends DeclarativeWebScript * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */ @Override - protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) { // retrieve ticket from request and current ticket String ticket = req.getExtensionPath(); if (ticket == null && ticket.length() == 0) { - throw new WebScriptException("Ticket not specified"); + throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Ticket not specified"); } + // construct model for ticket + Map model = new HashMap(7, 1.0f); + model.put("ticket", ticket); + try { String ticketUser = ticketComponent.validateTicket(ticket); @@ -85,20 +91,23 @@ public class LoginTicketDelete extends DeclarativeWebScript // do not go any further if tickets are different if (!AuthenticationUtil.getCurrentUserName().equals(ticketUser)) { - // TODO: 404 error - throw new WebScriptException("Ticket not found"); + status.setRedirect(true); + status.setCode(HttpServletResponse.SC_NOT_FOUND); + status.setMessage("Ticket not found"); + } + else + { + // delete the ticket + authenticationService.invalidateTicket(ticket); } } catch(AuthenticationException e) { - throw new WebScriptException("Ticket not found"); + status.setRedirect(true); + status.setCode(HttpServletResponse.SC_NOT_FOUND); + status.setMessage("Ticket not found"); } - // delete the ticket - authenticationService.invalidateTicket(ticket); - - Map model = new HashMap(7, 1.0f); - model.put("ticket", ticket); return model; } diff --git a/source/java/org/alfresco/web/scripts/bean/SearchEngines.java b/source/java/org/alfresco/web/scripts/bean/SearchEngines.java index cb013c3e29..7ef0ddc8c7 100644 --- a/source/java/org/alfresco/web/scripts/bean/SearchEngines.java +++ b/source/java/org/alfresco/web/scripts/bean/SearchEngines.java @@ -36,7 +36,7 @@ import org.alfresco.repo.content.MimetypeMap; import org.alfresco.web.config.OpenSearchConfigElement; import org.alfresco.web.scripts.DeclarativeWebScript; import org.alfresco.web.scripts.WebScriptRequest; -import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.WebScriptStatus; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -80,7 +80,7 @@ public class SearchEngines extends DeclarativeWebScript * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */ @Override - protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) { String urlType = req.getParameter("type"); if (urlType == null || urlType.length() == 0) diff --git a/source/java/org/alfresco/web/scripts/jsf/WebScriptJSFResponse.java b/source/java/org/alfresco/web/scripts/jsf/WebScriptJSFResponse.java index 10180fb793..9a7a0b0352 100644 --- a/source/java/org/alfresco/web/scripts/jsf/WebScriptJSFResponse.java +++ b/source/java/org/alfresco/web/scripts/jsf/WebScriptJSFResponse.java @@ -105,6 +105,13 @@ public class WebScriptJSFResponse implements WebScriptResponse return buf.toString(); } + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptResponse#reset() + */ + public void reset() + { + } + /** * @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream() */ @@ -121,6 +128,13 @@ public class WebScriptJSFResponse implements WebScriptResponse return fc.getResponseWriter(); } + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptResponse#setStatus(int) + */ + public void setStatus(int status) + { + } + /** * @see org.alfresco.web.scripts.WebScriptResponse#setContentType(java.lang.String) */ @@ -128,4 +142,5 @@ public class WebScriptJSFResponse implements WebScriptResponse { // Alfresco JSF framework only supports the default of text-html } + } diff --git a/source/java/org/alfresco/web/scripts/portlet/WebScriptPortletResponse.java b/source/java/org/alfresco/web/scripts/portlet/WebScriptPortletResponse.java index 90e6f66909..b01a88c173 100644 --- a/source/java/org/alfresco/web/scripts/portlet/WebScriptPortletResponse.java +++ b/source/java/org/alfresco/web/scripts/portlet/WebScriptPortletResponse.java @@ -66,6 +66,13 @@ public class WebScriptPortletResponse implements WebScriptResponse return res; } + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptResponse#setStatus(int) + */ + public void setStatus(int status) + { + } + /* (non-Javadoc) * @see org.alfresco.web.scripts.WebScriptResponse#setContentType(java.lang.String) */ @@ -74,6 +81,20 @@ public class WebScriptPortletResponse implements WebScriptResponse res.setContentType(contentType); } + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptResponse#reset() + */ + public void reset() + { + try + { + res.reset(); + } + catch(IllegalStateException e) + { + } + } + /* (non-Javadoc) * @see org.alfresco.web.scripts.WebScriptResponse#getWriter() */ @@ -105,5 +126,5 @@ public class WebScriptPortletResponse implements WebScriptResponse } return portletUrl.toString(); } - + }