Java tutorial
/* * The Fascinator - Portal - Dynamic Page Service * Copyright (C) 2008-2011 University of Southern Queensland * * 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. */ package com.googlecode.fascinator.portal.services.impl; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.StringWriter; import java.io.Writer; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.services.Request; import org.apache.tapestry5.services.RequestGlobals; import org.apache.tapestry5.services.Response; import org.apache.velocity.VelocityContext; import org.apache.velocity.context.Context; import org.python.core.Py; import org.python.core.PyObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.googlecode.fascinator.api.storage.DigitalObject; import com.googlecode.fascinator.api.storage.Storage; import com.googlecode.fascinator.common.IndexAndPayloadComposite; import com.googlecode.fascinator.common.JsonSimple; import com.googlecode.fascinator.common.JsonSimpleConfig; import com.googlecode.fascinator.common.StorageDataUtil; import com.googlecode.fascinator.common.solr.SolrDoc; import com.googlecode.fascinator.portal.FormData; import com.googlecode.fascinator.portal.JsonSessionState; import com.googlecode.fascinator.portal.guitoolkit.GUIToolkit; import com.googlecode.fascinator.portal.services.DynamicPageCache; import com.googlecode.fascinator.portal.services.DynamicPageService; import com.googlecode.fascinator.portal.services.HouseKeepingManager; import com.googlecode.fascinator.portal.services.PortalManager; import com.googlecode.fascinator.portal.services.PortalSecurityManager; import com.googlecode.fascinator.portal.services.ScriptingServices; import com.googlecode.fascinator.portal.services.VelocityService; /** * * * @author Oliver Lucido */ public class CachingDynamicPageServiceImpl implements DynamicPageService { /** Default layout template name */ private static final String DEFAULT_LAYOUT = "layout"; /** Extension for AJAX resources */ private static final String AJAX_EXT = ".ajax"; /** Extension for script resources */ private static final String SCRIPT_EXT = ".script"; /** Activation method for jython scripts */ private static final String SCRIPT_ACTIVATE_METHOD = "__activate__"; /** Logging */ private Logger log = LoggerFactory.getLogger(CachingDynamicPageServiceImpl.class); /** Tapestry HTTP servlet request support */ @Inject private RequestGlobals requestGlobals; /** HTTP Request */ @Inject private Request request; /** HTTP Response */ @Inject private Response response; /** Services to expose to the jython scripts */ @Inject private ScriptingServices scriptingServices; /** House keeping */ @Inject private HouseKeepingManager houseKeeping; /** Security manager */ @Inject private PortalSecurityManager security; /** Page caching support */ @Inject private DynamicPageCache pageCache; /** Velocity service */ private VelocityService velocityService; /** System configuration */ private JsonSimpleConfig config; /** Layout template name */ private String layoutName; /** Application base URL */ private String urlBase; /** Absolute path to portal base directory */ private String portalPath; /** GUI toolkit */ private GUIToolkit toolkit; /** Default fallback portal id */ private String defaultPortal; /** Default display template */ private String defaultDisplay; /** Default version string */ private String versionString; /** * Constructs and configures the service. * * @param portalManager PortalManager instance * @param velocityService VelocityService instance */ public CachingDynamicPageServiceImpl(PortalManager portalManager, VelocityService velocityService) { this.velocityService = velocityService; try { config = new JsonSimpleConfig(); layoutName = config.getString(DEFAULT_LAYOUT, "portal", "layout"); urlBase = config.getString(null, "urlBase"); versionString = config.getString(null, "version.string"); toolkit = new GUIToolkit(); portalPath = portalManager.getHomeDir().getAbsolutePath(); defaultPortal = portalManager.getDefaultPortal(); defaultDisplay = portalManager.getDefaultDisplay(); } catch (Exception e) { throw new RuntimeException(e); } } /** * Gets a Velocity resource. This method is deprecated, please use * VelocityService.getResource() instead. * * @param resourcePath valid Velocity resource path * @return resource stream or null if not found */ @Override @Deprecated public InputStream getResource(String resourcePath) { log.warn("getResource() is deprecated, use VelocityService.getResource() ({})", resourcePath); return velocityService.getResource(resourcePath); } /** * Gets a Velocity resource. This method is deprecated, please use * VelocityService.getResource() instead. * * @param portalId the portal to get the resource from * @param resourceName the resource to get * @return resource stream or null if not found */ @Override @Deprecated public InputStream getResource(String portalId, String resourceName) { log.warn("getResource() is deprecated, use VelocityService.getResource() ({}/{})", portalId, resourceName); return velocityService.getResource(portalId, resourceName); } /** * Resolves the given resource to a valid Velocity resource if possible. * This method is deprecated, please use VelocityService.resourceExists() * instead. * * @param portalId the portal to get the resource from * @param resourceName the resource to check * @return a valid Velocity resource path or null if not found */ @Override @Deprecated public String resourceExists(String portalId, String resourceName) { String resourcePath = velocityService.resourceExists(portalId, resourceName); log.warn("resourceExists() is deprecated, use VelocityService.resourceExists() ({})", resourcePath); return resourcePath; } /** * Renders the Velocity template with the specified form data. * * @param portalId the portal to get the template from * @param pageName the page template to render * @param out render results will be written to this output stream * @param formData request form data * @param sessionState current session * @return MIME type of the response */ @Override public String render(String portalId, String pageName, OutputStream out, FormData formData, JsonSessionState sessionState) { // remove extension for special cases boolean isAjax = pageName.endsWith(AJAX_EXT); boolean isScript = pageName.endsWith(SCRIPT_EXT); if (isAjax || isScript) { pageName = FilenameUtils.removeExtension(pageName); } // setup script and velocity context String contextPath = request.getContextPath(); int serverPort = requestGlobals.getHTTPServletRequest().getServerPort(); Map<String, Object> bindings = new HashMap<String, Object>(); bindings.put("systemConfig", config); bindings.put("Services", scriptingServices); bindings.put("systemProperties", System.getProperties()); bindings.put("request", request); bindings.put("httpServletRequest", requestGlobals.getHTTPServletRequest()); bindings.put("response", response); bindings.put("formData", formData); bindings.put("sessionState", sessionState); bindings.put("security", security); bindings.put("contextPath", contextPath); bindings.put("scriptsPath", portalPath + "/" + portalId + "/scripts"); bindings.put("portalDir", portalPath + "/" + portalId); bindings.put("portalId", portalId); String urlBase = this.urlBase; if (StringUtils.isEmpty(urlBase)) { urlBase = getURL(requestGlobals.getHTTPServletRequest(), config); } else if (config.getString(null, "portal", "urlBases", portalId) != null) { urlBase = config.getString(null, "portal", "urlBases", portalId); } bindings.put("urlBase", urlBase); if (versionString == null) { bindings.put("portalPath", urlBase + portalId); } else { bindings.put("portalPath", urlBase + "verNum" + versionString + "/" + portalId); } bindings.put("defaultPortal", defaultPortal); bindings.put("pageName", pageName); bindings.put("serverPort", serverPort); bindings.put("toolkit", toolkit); bindings.put("log", log); bindings.put("notifications", houseKeeping.getUserMessages()); bindings.put("bindings", bindings); StorageDataUtil dataUtil = new StorageDataUtil(); bindings.put("jsonUtil", dataUtil); // run page and template scripts Set<String> messages = new HashSet<String>(); bindings.put("page", evalScript(portalId, layoutName, bindings, messages)); bindings.put("StringUtils", StringUtils.class); bindings.put("StringEscapeUtils", StringEscapeUtils.class); bindings.put("self", evalScript(portalId, pageName, bindings, messages)); // try to return the proper MIME type String mimeType = "text/html"; Object mimeTypeAttr = request.getAttribute("Content-Type"); if (mimeTypeAttr != null) { mimeType = mimeTypeAttr.toString(); } // stop here if the scripts have already sent a response boolean committed = response.isCommitted(); if (committed) { // log.debug("Response has been sent or redirected"); return mimeType; } if (velocityService.resourceExists(portalId, pageName + ".vm") != null) { // set up the velocity context VelocityContext vc = new VelocityContext(); for (String key : bindings.keySet()) { vc.put(key, bindings.get(key)); } vc.put("velocityContext", vc); if (!messages.isEmpty()) { vc.put("renderMessages", messages); } try { // render the page content log.debug("Rendering page {}/{}.vm...", portalId, pageName); StringWriter pageContentWriter = new StringWriter(); velocityService.renderTemplate(portalId, pageName, vc, pageContentWriter); if (isAjax || isScript) { out.write(pageContentWriter.toString().getBytes("UTF-8")); } else { vc.put("pageContent", pageContentWriter.toString()); } } catch (Exception e) { ByteArrayOutputStream eOut = new ByteArrayOutputStream(); e.printStackTrace(new PrintStream(eOut)); String eMsg = eOut.toString(); log.error("Failed to render page ({})!\n=====\n{}\n=====", isAjax ? "ajax" : (isScript ? "script" : "html"), eMsg); String errorMsg = "<pre>Page content template error: " + pageName + "\n" + eMsg + "</pre>"; if (isAjax || isScript) { try { out.write(errorMsg.getBytes()); } catch (Exception e2) { log.error("Failed to output error message!"); } } else { vc.put("pageContent", errorMsg); } } if (!(isAjax || isScript)) { try { // render the page using the layout template log.debug("Rendering layout {}/{}.vm for page {}.vm...", new Object[] { portalId, layoutName, pageName }); Writer pageWriter = new OutputStreamWriter(out, "UTF-8"); velocityService.renderTemplate(portalId, layoutName, vc, pageWriter); pageWriter.close(); } catch (Exception e) { throw new RuntimeException(e); } } } return mimeType; } @Override public String renderObject(Context context, String template, IndexAndPayloadComposite metadata) { context.put("metadata", metadata); return renderObject(context, template, metadata.getIndexedData()); } /** * Renders a display template. This is generally used by calling the * #parseDisplayTemplate() method in the portal-library.vm. * * @param context Velocity context * @param template display template name * @param metadata Solr metadata * @return rendered content */ @Override public String renderObject(Context context, String template, SolrDoc metadata) { // log.debug("========== START renderObject =========="); // setup script and velocity context String portalId = context.get("portalId").toString(); String displayType = metadata.getString(defaultDisplay, "display_type"); if ("".equals(displayType)) { displayType = defaultDisplay; } // On the detail page, check for a preview template too if (template.startsWith("detail")) { String previewType = metadata.getString(null, "preview_type"); if (previewType != null && !"".equals(previewType)) { log.debug("Preview template found: '{}'", previewType); displayType = previewType; } } String templateName = "display/" + displayType + "/" + template; // log.debug("displayType: '{}'", displayType); // log.debug("templateName: '{}'", templateName); Object parentPageObject = null; Context objectContext = new VelocityContext(context); if (objectContext.containsKey("parent")) { parentPageObject = objectContext.get("parent"); } else { parentPageObject = objectContext.get("self"); } // log.debug("parentPageObject: '{}'", parentPageObject); // evaluate the context script if exists Set<String> messages = null; if (objectContext.containsKey("renderMessages")) { messages = (Set<String>) objectContext.get("renderMessages"); } else { messages = new HashSet<String>(); context.put("renderMessages", messages); } // get the DigitalObject to replace the metadata with the tfpackage Storage storage = scriptingServices.getStorage(); JsonSimple tfpackage = null; IndexAndPayloadComposite compositeData = (objectContext.get("metadata") instanceof IndexAndPayloadComposite ? (IndexAndPayloadComposite) objectContext.get("metadata") : null); // check if there's already a composite... if (compositeData == null) { compositeData = loadComposite(storage, compositeData, metadata, templateName, messages); } else { // if there's a composite but there's no main payload, attempt to // reload it if (compositeData.getPayloadData() == null) { loadComposite(storage, compositeData, metadata, templateName, messages); } tfpackage = compositeData.getPayloadData(); } objectContext.put("pageName", template); objectContext.put("displayType", displayType); objectContext.put("parent", parentPageObject); objectContext.put("indexedData", metadata); objectContext.put("metadata", compositeData); objectContext.put("tfpackage", tfpackage); Map<String, Object> bindings = (Map<String, Object>) objectContext.get("bindings"); bindings.put("metadata", compositeData); bindings.put("indexedData", metadata); bindings.put("tfpackage", tfpackage); objectContext.put("self", evalScript(portalId, templateName, bindings, messages)); String content = ""; try { // render the page content log.debug("Rendering display page {}/{}.vm...", portalId, templateName); StringWriter pageContentWriter = new StringWriter(); velocityService.renderTemplate(portalId, templateName, objectContext, pageContentWriter); content = pageContentWriter.toString(); } catch (Exception e) { log.error("Failed rendering display page: {}", templateName); ByteArrayOutputStream eOut = new ByteArrayOutputStream(); e.printStackTrace(new PrintStream(eOut)); String eMsg = eOut.toString(); messages.add("Page content template error: " + templateName + "\n" + eMsg); } // log.debug("========== END renderObject =========="); return content; } /** * Attempts to load the main data payload from storage. * * Supports reloading the payload to an existing instance of the composite. * * Adds the pay * * @param storage * @param compositeData * @param metadata * @param templateName * @param messages * @return */ private IndexAndPayloadComposite loadComposite(Storage storage, IndexAndPayloadComposite compositeData, SolrDoc metadata, String templateName, Set<String> messages) { DigitalObject obj = null; JsonSimple tfpackage = null; try { obj = storage.getObject(metadata.get("id")); if (obj != null) { for (String payloadId : obj.getPayloadIdList()) { if (payloadId.endsWith("tfpackage")) { tfpackage = new JsonSimple(obj.getPayload(payloadId).open()); break; } } if (compositeData == null) { compositeData = new IndexAndPayloadComposite(metadata, tfpackage); } else { compositeData.setPayloadData(tfpackage); } } else { log.error("Failed rendering display page: {}", templateName); messages.add("DigitalObject not found in storage!"); } } catch (Exception e) { log.error("Failed rendering display page: {}", templateName); ByteArrayOutputStream eOut = new ByteArrayOutputStream(); e.printStackTrace(new PrintStream(eOut)); String eMsg = eOut.toString(); messages.add("Page content template error: " + templateName + "\n" + eMsg); } return compositeData; } /** * Run the jython script with the given context. This method only calls the * activation method on the jython script objects which are retrieved from * the page cache. * * @param portalId portal to get the script from * @param pageName page name that the script is supporting * @param context context for the script * @param messages a list to append error messages to if necessary * @return the jython script object */ private PyObject evalScript(String portalId, String pageName, Map<String, Object> context, Set<String> messages) { PyObject scriptObject = null; String scriptName = "scripts/" + pageName + ".py"; try { String path = velocityService.resourceExists(portalId, scriptName); if (path == null) { log.debug("No script for portalId:'{}' scriptName:'{}'", portalId, scriptName); } else { scriptObject = pageCache.getScriptObject(path); if (scriptObject.__findattr__(SCRIPT_ACTIVATE_METHOD) != null) { // log.debug("activating '{}' within thread '{}'", // scriptObject, Thread.currentThread().getId()); scriptObject.invoke(SCRIPT_ACTIVATE_METHOD, Py.java2py(context)); } else { log.warn("{} method not found for scriptPath:'{}'", SCRIPT_ACTIVATE_METHOD, path); } } } catch (Exception e) { ByteArrayOutputStream eOut = new ByteArrayOutputStream(); e.printStackTrace(new PrintStream(eOut)); String eMsg = eOut.toString(); log.warn("Failed to run script!\n=====\n{}\n=====", eMsg); messages.add("Script error: " + scriptName + "\n" + eMsg); } return scriptObject; } public static String getURL(HttpServletRequest req, JsonSimpleConfig config) { // Sometimes when proxying https behind apache or another web server, if // this is managed by the web server the scheme may be reported // incorrectly to Fascinator String scheme = config.getString(req.getScheme(), "urlSchemeName"); String serverName = req.getServerName(); // hostname.com int serverPort = req.getServerPort(); // 80 String contextPath = req.getContextPath(); // /redbox // Reconstruct original requesting URL StringBuilder url = new StringBuilder(); url.append(scheme).append("://").append(serverName); if ((serverPort != 80) && (serverPort != 443)) { url.append(":").append(serverPort); } url.append(contextPath).append("/"); return url.toString(); } }