/* * 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 java.io.IOException; import java.util.HashMap; import java.util.Map; 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.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.security.AuthorityService; 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; /** * Encapsulates the execution of a single Web Script. * * Provides support for logging, transactions and authentication. * * Sub-classes of WebScriptRuntime maintain the execution environment e.g. servlet * request & response. * * A new instance of WebScriptRuntime is required for each new execution environment. * * @author davidc */ public abstract class WebScriptRuntime { // Logger protected static final Log logger = LogFactory.getLog(WebScriptRuntime.class); /** Component Dependencies */ private WebScriptRegistry registry; private RetryingTransactionHelper retryingTransactionHelper; private AuthorityService authorityService; /** * Construct * * @param registry web script registry * @param transactionService transaction service */ public WebScriptRuntime(WebScriptRegistry registry, RetryingTransactionHelper transactionHelper, AuthorityService authorityService) { this.registry = registry; this.retryingTransactionHelper = transactionHelper; this.authorityService = authorityService; } /** * Execute the Web Script encapsulated by this Web Script Runtime */ public void executeScript() { long startRuntime = System.currentTimeMillis(); String method = getScriptMethod(); 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 || match.getKind() == WebScriptMatch.Kind.URI) { 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() + ", 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 RetryingTransactionCallback work = new RetryingTransactionCallback() { public Object execute() throws Exception { 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) { retryingTransactionHelper.doInTransaction(work); } else { retryingTransactionHelper.doInTransaction(work, false, true); } } } finally { if (logger.isDebugEnabled()) { 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(); } // 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 " + templatePath + " (format: " + WebScriptResponse.HTML_FORMAT + ")"); } } // render output if (logger.isDebugEnabled()) { logger.debug("Force success status header in response: " + req.forceSuccessStatus()); logger.debug("Sending status " + statusCode + " (Template: " + templatePath + ")"); logger.debug("Rendering response: content type=" + MimetypeMap.MIMETYPE_HTML); } res.reset(); res.setStatus(req.forceSuccessStatus() ? HttpServletResponse.SC_OK : 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 { long endRuntime = System.currentTimeMillis(); if (logger.isDebugEnabled()) logger.debug("Processed script url (" + method + ") " + scriptUrl + " in " + (endRuntime - startRuntime) + "ms"); } } /** * Execute script whilst authenticated * * @param scriptReq Web Script Request * @param scriptRes Web Script Response * @throws IOException */ protected void authenticatedExecute(WebScriptRequest scriptReq, WebScriptResponse scriptRes) throws IOException { WebScript script = scriptReq.getServiceMatch().getWebScript(); WebScriptDescription desc = script.getDescription(); RequiredAuthentication required = desc.getRequiredAuthentication(); boolean isGuest = scriptReq.isGuest(); if (required == RequiredAuthentication.none) { wrappedExecute(scriptReq, scriptRes); } else if ((required == RequiredAuthentication.user || required == RequiredAuthentication.admin) && isGuest) { throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access."); } else { String currentUser = null; try { // // Determine if user already authenticated // currentUser = AuthenticationUtil.getCurrentUserName(); if (logger.isDebugEnabled()) { logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); logger.debug("Authentication required: " + required); logger.debug("Guest login: " + isGuest); } // // Apply appropriate authentication to Web Script invocation // if (authenticate(required, isGuest)) { if (required == RequiredAuthentication.admin && !authorityService.hasAdminAuthority()) { throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires admin authentication; however, a non-admin has attempted access."); } // Execute Web Script wrappedExecute(scriptReq, scriptRes); } } finally { // // Reset authentication for current thread // AuthenticationUtil.clearCurrentSecurityContext(); if (currentUser != null) { AuthenticationUtil.setCurrentUser(currentUser); } if (logger.isDebugEnabled()) logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); } } } /** * Execute Web Script with pre & post hooks * * @param scriptReq Web Script Request * @param scriptRes Web Script Response * @throws IOException */ protected void wrappedExecute(WebScriptRequest scriptReq, WebScriptResponse scriptRes) throws IOException { boolean execute = preExecute(scriptReq, scriptRes); if (execute) { WebScript script = scriptReq.getServiceMatch().getWebScript(); script.execute(scriptReq, scriptRes); postExecute(scriptReq, scriptRes); } } /** * Get the Web Script Method e.g. get, post * * @return web script method */ protected abstract String getScriptMethod(); /** * Get the Web Script Url * * @return web script url */ protected abstract String getScriptUrl(); /** * Create a Web Script Request * * @param match web script matching the script method and url * @return web script request */ protected abstract WebScriptRequest createRequest(WebScriptMatch match); /** * Create a Web Script Response * * @return web script response */ protected abstract WebScriptResponse createResponse(); /** * Authenticate Web Script execution * * @param required required level of authentication * @param isGuest is the request accessed as Guest * * @return true if authorised, false otherwise */ protected abstract boolean authenticate(RequiredAuthentication required, boolean isGuest); /** * Pre-execution hook * * @param scriptReq Web Script Request * @param scriptRes Web Script Response * @return true => execute script, false => do not execute script */ protected boolean preExecute(WebScriptRequest scriptReq, WebScriptResponse scriptRes) { return true; } /** * Post-execution hook * * Note: this hook is not invoked if the script is not executed. * * @param scriptReq Web Script Request * @param scriptRes Web Script Response */ protected void postExecute(WebScriptRequest scriptReq, WebScriptResponse scriptRes) { } }