Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.click.service; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeMap; import javax.servlet.ServletContext; import ognl.Ognl; import org.apache.click.Control; import org.apache.click.Page; import org.apache.click.PageInterceptor; import org.apache.click.util.Bindable; import org.apache.click.util.ClickUtils; import org.apache.click.util.Format; import org.apache.click.util.HtmlStringBuffer; import org.apache.click.util.PropertyUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * Provides a Click XML configuration service class. * <p/> * This class reads Click configuration information from a file named * <tt>click.xml</tt>. The service will first lookup the <tt>click.xml</tt> * under the applications <tt>WEB-INF</tt> directory, and if not found * attempt to load the configuration file from the classpath root. * <p/> * Configuring Click through the <tt>click.xml</tt> file is the most common * technique. * <p/> * However you can instruct Click to use a different service implementation. * Please see {@link ConfigService} for more details. */ public class XmlConfigService implements ConfigService, EntityResolver { /** The name of the Click logger: "<tt>org.apache.click</tt>". */ static final String CLICK_LOGGER = "org.apache.click"; /** The click deployment directory path: "/click". */ static final String CLICK_PATH = "/click"; /** The default common page headers. */ static final Map<String, Object> DEFAULT_HEADERS; /** * The default velocity properties filename: * "<tt>/WEB-INF/velocity.properties</tt>". */ static final String DEFAULT_VEL_PROPS = "/WEB-INF/velocity.properties"; /** The click DTD file name: "<tt>click.dtd</tt>". */ static final String DTD_FILE_NAME = "click.dtd"; /** * The resource path of the click DTD file: * "<tt>/org/apache/click/click.dtd</tt>". */ static final String DTD_FILE_PATH = "/org/apache/click/" + DTD_FILE_NAME; /** * The user supplied macro file name: "<tt>macro.vm</tt>". */ static final String MACRO_VM_FILE_NAME = "macro.vm"; /** The production application mode. */ static final int PRODUCTION = 0; /** The profile application mode. */ static final int PROFILE = 1; /** The development application mode. */ static final int DEVELOPMENT = 2; /** The debug application mode. */ static final int DEBUG = 3; /** The trace application mode. */ static final int TRACE = 4; static final String[] MODE_VALUES = { "production", "profile", "development", "debug", "trace" }; private static final Object PAGE_LOAD_LOCK = new Object(); /** * The name of the Velocity logger: "<tt>org.apache.velocity</tt>". */ static final String VELOCITY_LOGGER = "org.apache.velocity"; /** * The global Velocity macro file name: * "<tt>VM_global_library.vm</tt>". */ static final String VM_FILE_NAME = "VM_global_library.vm"; /** Initialize the default headers. */ static { DEFAULT_HEADERS = new HashMap<String, Object>(); DEFAULT_HEADERS.put("Pragma", "no-cache"); DEFAULT_HEADERS.put("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0"); DEFAULT_HEADERS.put("Expires", new Date(1L)); } private static final String GOOGLE_APP_ENGINE = "Google App Engine"; // ------------------------------------------------ Package Private Members /** The Map of global page headers. */ Map commonHeaders; /** The page automapping override page class for path list. */ final List excludesList = new ArrayList(); /** The map of ClickApp.PageElm keyed on path. */ final Map pageByPathMap = new HashMap(); /** The map of ClickApp.PageElm keyed on class. */ final Map pageByClassMap = new HashMap(); /** The list of page packages. */ final List pagePackages = new ArrayList(); // -------------------------------------------------------- Private Members /** The automatically bind controls, request parameters and models flag. */ private AutoBinding autobinding; /** The Commons FileUpload service class. */ private FileUploadService fileUploadService; /** The format class. */ private Class<? extends Format> formatClass; /** The character encoding of this application. */ private String charset; /** The default application locale.*/ private Locale locale; /** The application log service. */ private LogService logService; /** * The application mode: * [ PRODUCTION | PROFILE | DEVELOPMENT | DEBUG | TRACE ]. */ private int mode; /** The list of application page interceptor instances. */ private List<PageInterceptorConfig> pageInterceptorConfigList = new ArrayList<PageInterceptorConfig>(); /** The ServletContext instance. */ private ServletContext servletContext; /** The application ResourceService. */ private ResourceService resourceService; /** The application TemplateService. */ private TemplateService templateService; /** The application TemplateService. */ private MessagesMapService messagesMapService; /** Flag indicating whether Click is running on Google App Engine. */ private boolean onGoogleAppEngine = false; // --------------------------------------------------------- Public Methods /** * @see ConfigService#onInit(ServletContext) * * @param servletContext the application servlet context * @throws Exception if an error occurs initializing the application */ public void onInit(ServletContext servletContext) throws Exception { Validate.notNull(servletContext, "Null servletContext parameter"); this.servletContext = servletContext; onGoogleAppEngine = servletContext.getServerInfo().startsWith(GOOGLE_APP_ENGINE); // Set default logService early to log errors when services fail. logService = new ConsoleLogService(); messagesMapService = new DefaultMessagesMapService(); InputStream inputStream = ClickUtils.getClickConfig(servletContext); try { Document document = ClickUtils.buildDocument(inputStream, this); Element rootElm = document.getDocumentElement(); // Load the log service loadLogService(rootElm); // Load the application mode and set the logger levels loadMode(rootElm); if (logService.isInfoEnabled()) { logService.info("*** Initializing Click " + ClickUtils.getClickVersion() + " in " + getApplicationMode() + " mode ***"); String msg = "initialized LogService: " + logService.getClass().getName(); getLogService().info(msg); } // Deploy click resources deployFiles(rootElm); // Load the format class loadFormatClass(rootElm); // Load the common headers loadHeaders(rootElm); // Load the pages loadPages(rootElm); // Load the error and not-found pages loadDefaultPages(); // Load the charset loadCharset(rootElm); // Load the locale loadLocale(rootElm); // Load the File Upload service loadFileUploadService(rootElm); // Load the Templating service loadTemplateService(rootElm); // Load the Resource service loadResourceService(rootElm); // Load the Messages Map service loadMessagesMapService(rootElm); // Load the PageInterceptors loadPageInterceptors(rootElm); } finally { ClickUtils.close(inputStream); } } /** * @see ConfigService#onDestroy() */ public void onDestroy() { if (getFileUploadService() != null) { getFileUploadService().onDestroy(); } if (getTemplateService() != null) { getTemplateService().onDestroy(); } if (getResourceService() != null) { getResourceService().onDestroy(); } if (getMessagesMapService() != null) { getMessagesMapService().onDestroy(); } if (getLogService() != null) { getLogService().onDestroy(); } } // --------------------------------------------------------- Public Methods /** * Return the application mode String value: <tt>["production", * "profile", "development", "debug"]</tt>. * * @return the application mode String value */ public String getApplicationMode() { return MODE_VALUES[mode]; } /** * @see ConfigService#getCharset() * * @return the application character encoding */ public String getCharset() { return charset; } /** * @see ConfigService#getFileUploadService() * * @return the FileUpload service */ public FileUploadService getFileUploadService() { return fileUploadService; } /** * @see ConfigService#getLogService() * * @return the application log service. */ public LogService getLogService() { return logService; } /** * @see ConfigService#getResourceService() * * @return the resource service */ public ResourceService getResourceService() { return resourceService; } /** * @see ConfigService#getTemplateService() * * @return the template service */ public TemplateService getTemplateService() { return templateService; } /** * @see ConfigService#getMessagesMapService() * * @return the messages map service */ public MessagesMapService getMessagesMapService() { return messagesMapService; } /** * @see ConfigService#createFormat() * * @return a new format object */ public Format createFormat() { try { return formatClass.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } /** * @see ConfigService#getLocale() * * @return the application locale */ public Locale getLocale() { return locale; } /** * @see ConfigService#getAutoBindingMode() * * @return the Page field auto binding mode { PUBLIC, ANNOTATION, NONE } */ public AutoBinding getAutoBindingMode() { return autobinding; } /** * @see ConfigService#isProductionMode() * * @return true if the application is in "production" mode */ public boolean isProductionMode() { return (mode == PRODUCTION); } /** * @see ConfigService#isProfileMode() * * @return true if the application is in "profile" mode */ public boolean isProfileMode() { return (mode == PROFILE); } /** * @see ConfigService#isJspPage(String) * * @param path the Page ".htm" path * @return true if JSP exists for the given ".htm" path */ public boolean isJspPage(String path) { HtmlStringBuffer buffer = new HtmlStringBuffer(); int index = StringUtils.lastIndexOf(path, "."); if (index > 0) { buffer.append(path.substring(0, index)); } else { buffer.append(path); } buffer.append(".jsp"); return pageByPathMap.containsKey(buffer.toString()); } /** * Return true if the given path is a Page class template, false * otherwise. By default this method returns true if the path has a * <tt>.htm</tt> or <tt>.jsp</tt> extension. * <p/> * If you want to map alternative templates besides <tt>.htm</tt> and * <tt>.jsp</tt> files you can override this method and provide extra * checks against the given path whether it should be added as a * template or not. * <p/> * Below is an example showing how to allow <tt>.xml</tt> paths to * be recognized as Page class templates. * * <pre class="prettyprint"> * public class MyConfigService extends XmlConfigService { * * protected boolean isTemplate(String path) { * // invoke default implementation * boolean isTemplate = super.isTemplate(path); * * if (!isTemplate) { * // If path has an .xml extension, mark it as a template * isTemplate = path.endsWith(".xml"); * } * return isTemplate; * } * } </pre> * * Here is an example <tt>web.xml</tt> showing how to configure a custom * ConfigService through the context parameter <tt>config-service-class</tt>. * We also map <tt>*.xml</tt> requests to be routed through ClickServlet: * * <pre class="prettyprint"> * <web-app xmlns="http://java.sun.com/xml/ns/j2ee" * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" * xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" * version="2.4"> * * <!-- Specify a custom ConfigService through the context param 'config-service-class' --> * <context-param> * <param-name>config-service-class</param-name> * <param-value>com.mycorp.service.MyConfigSerivce</param-value> * </context-param> * * <servlet> * <servlet-name>ClickServlet</servlet-name> * <servlet-class>org.apache.click.ClickServlet</servlet-class> * <load-on-startup>0</load-on-startup> * </servlet> * * <!-- NOTE: we still map the .htm extension --> * <servlet-mapping> * <servlet-name>ClickServlet</servlet-name> * <url-pattern>*.htm</url-pattern> * </servlet-mapping> * * <!-- NOTE: we also map .xml extension in order to route xml requests to the ClickServlet --> * <servlet-mapping> * <servlet-name>ClickServlet</servlet-name> * <url-pattern>*.xml</url-pattern> * </servlet-mapping> * * ... * * </web-app> </pre> * * <b>Please note</b>: even though you can add extra template mappings by * overriding this method, it is still recommended to keep the default * <tt>.htm</tt> mapping by invoking <tt>super.isTemplate(String)</tt>. * The reason being that Click ships with some default templates such as * {@link ConfigService#ERROR_PATH} and {@link ConfigService#NOT_FOUND_PATH} * that must be mapped as <tt>.htm</tt>. * <p/> * Please see the ConfigService <a href="#config">javadoc</a> for details * on how to configure a custom ConfigService implementation. * * @see ConfigService#isTemplate(String) * * @param path the path to check if it is a Page class template or not * @return true if the path is a Page class template, false otherwise */ public boolean isTemplate(String path) { if (path.endsWith(".htm") || path.endsWith(".jsp")) { return true; } return false; } /** * @see ConfigService#getPageClass(String) * * @param path the page path * @return the page class for the given path or null if no class is found */ public Class<? extends Page> getPageClass(String path) { // If in production or profile mode. if (mode <= PROFILE) { PageElm page = (PageElm) pageByPathMap.get(path); if (page == null) { String jspPath = StringUtils.replace(path, ".htm", ".jsp"); page = (PageElm) pageByPathMap.get(jspPath); } if (page != null) { return page.getPageClass(); } else { return null; } // Else in development, debug or trace mode } else { synchronized (PAGE_LOAD_LOCK) { PageElm page = (PageElm) pageByPathMap.get(path); if (page == null) { String jspPath = StringUtils.replace(path, ".htm", ".jsp"); page = (PageElm) pageByPathMap.get(jspPath); } if (page != null) { return page.getPageClass(); } Class pageClass = null; try { URL resource = servletContext.getResource(path); if (resource != null) { for (int i = 0; i < pagePackages.size(); i++) { String pagesPackage = pagePackages.get(i).toString(); pageClass = getPageClass(path, pagesPackage); if (pageClass != null) { page = new PageElm(path, pageClass, commonHeaders, autobinding); pageByPathMap.put(page.getPath(), page); addToClassMap(page); if (logService.isDebugEnabled()) { String msg = path + " -> " + pageClass.getName(); logService.debug(msg); } break; } } } } catch (MalformedURLException e) { //ignore } return pageClass; } } } /** * @see ConfigService#getPagePath(Class) * * @param pageClass the page class * @return path the page path or null if no path is found * @throws IllegalArgumentException if the Page Class is not configured * with a unique path */ public String getPagePath(Class<? extends Page> pageClass) { Object object = pageByClassMap.get(pageClass); if (object instanceof XmlConfigService.PageElm) { XmlConfigService.PageElm page = (XmlConfigService.PageElm) object; return page.getPath(); } else if (object instanceof List) { HtmlStringBuffer buffer = new HtmlStringBuffer(); buffer.append("Page class resolves to multiple paths: "); buffer.append(pageClass.getName()); buffer.append(" -> ["); for (Iterator it = ((List) object).iterator(); it.hasNext();) { PageElm pageElm = (PageElm) it.next(); buffer.append(pageElm.getPath()); if (it.hasNext()) { buffer.append(", "); } } buffer.append("]\nUse Context.createPage(String), or Context.getPageClass(String) instead."); throw new IllegalArgumentException(buffer.toString()); } else { return null; } } /** * @see ConfigService#getPageClassList() * * @return the list of configured page classes */ public List getPageClassList() { List classList = new ArrayList(pageByClassMap.size()); Iterator i = pageByClassMap.keySet().iterator(); while (i.hasNext()) { Class pageClass = (Class) i.next(); classList.add(pageClass); } return classList; } /** * @see ConfigService#getPageHeaders(String) * * @param path the path of the page * @return a Map of headers for the given page path */ public Map<String, Object> getPageHeaders(String path) { PageElm page = (PageElm) pageByPathMap.get(path); if (page == null) { String jspPath = StringUtils.replace(path, ".htm", ".jsp"); page = (PageElm) pageByPathMap.get(jspPath); } if (page != null) { return page.getHeaders(); } else { return null; } } /** * @see ConfigService#getNotFoundPageClass() * * @return the page not found <tt>Page</tt> <tt>Class</tt> */ public Class<? extends Page> getNotFoundPageClass() { PageElm page = (PageElm) pageByPathMap.get(NOT_FOUND_PATH); if (page != null) { return page.getPageClass(); } else { return org.apache.click.Page.class; } } /** * @see ConfigService#getErrorPageClass() * * @return the error handling page <tt>Page</tt> <tt>Class</tt> */ public Class<? extends Page> getErrorPageClass() { PageElm page = (PageElm) pageByPathMap.get(ERROR_PATH); if (page != null) { return page.getPageClass(); } else { return org.apache.click.util.ErrorPage.class; } } /** * @see ConfigService#getPageField(Class, String) * * @param pageClass the page class * @param fieldName the name of the field * @return the public field of the pageClass with the given name or null */ public Field getPageField(Class<? extends Page> pageClass, String fieldName) { return getPageFields(pageClass).get(fieldName); } /** * @see ConfigService#getPageFieldArray(Class) * * @param pageClass the page class * @return an array public fields for the given page class */ public Field[] getPageFieldArray(Class<? extends Page> pageClass) { Object object = pageByClassMap.get(pageClass); if (object instanceof XmlConfigService.PageElm) { XmlConfigService.PageElm page = (XmlConfigService.PageElm) object; return page.getFieldArray(); } else if (object instanceof List) { List list = (List) object; XmlConfigService.PageElm page = (XmlConfigService.PageElm) list.get(0); return page.getFieldArray(); } else { return null; } } /** * @see ConfigService#getPageFields(Class) * * @param pageClass the page class * @return a Map of public fields for the given page class */ public Map<String, Field> getPageFields(Class<? extends Page> pageClass) { Object object = pageByClassMap.get(pageClass); if (object instanceof XmlConfigService.PageElm) { XmlConfigService.PageElm page = (XmlConfigService.PageElm) object; return page.getFields(); } else if (object instanceof List) { List list = (List) object; XmlConfigService.PageElm page = (XmlConfigService.PageElm) list.get(0); return page.getFields(); } else { return Collections.emptyMap(); } } /** * @see ConfigService#getPageInterceptors() * * @return the list of configured PageInterceptor instances */ public List<PageInterceptor> getPageInterceptors() { if (pageInterceptorConfigList.isEmpty()) { return Collections.emptyList(); } List<PageInterceptor> interceptorList = new ArrayList<PageInterceptor>(pageInterceptorConfigList.size()); for (PageInterceptorConfig pageInterceptorConfig : pageInterceptorConfigList) { interceptorList.add(pageInterceptorConfig.getPageInterceptor()); } return interceptorList; } /** * @see ConfigService#getServletContext() * * @return the application servlet context */ public ServletContext getServletContext() { return servletContext; } /** * This method resolves the click.dtd for the XML parser using the * classpath resource: <tt>/org/apache/click/click.dtd</tt>. * * @see EntityResolver#resolveEntity(String, String) * * @param publicId the DTD public id * @param systemId the DTD system id * @return resolved entity DTD input stream * @throws SAXException if an error occurs parsing the document * @throws IOException if an error occurs reading the document */ public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { InputStream inputStream = ClickUtils.getResourceAsStream(DTD_FILE_PATH, getClass()); if (inputStream != null) { return new InputSource(inputStream); } else { throw new IOException("could not load resource: " + DTD_FILE_PATH); } } // ------------------------------------------------------ Protected Methods /** * Find and return the page class for the specified pagePath and * pagesPackage. * <p/> * For example if the pagePath is <tt>'/edit-customer.htm'</tt> and * package is <tt>'com.mycorp'</tt>, the matching page class will be: * <tt>com.mycorp.EditCustomer</tt> or <tt>com.mycorp.EditCustomerPage</tt>. * <p/> * If the page path is <tt>'/admin/add-customer.htm'</tt> and package is * <tt>'com.mycorp'</tt>, the matching page class will be: * <tt>com.mycorp.admin.AddCustomer</tt> or * <tt>com.mycorp.admin.AddCustomerPage</tt>. * * @param pagePath the path used for matching against a page class name * @param pagesPackage the package of the page class * @return the page class for the specified pagePath and pagesPackage */ protected Class<? extends Page> getPageClass(String pagePath, String pagesPackage) { // To understand this method lets walk through an example as the // code plays out. Imagine this method is called with the arguments: // pagePath='/pages/edit-customer.htm' // pagesPackage='org.apache.click' String packageName = ""; if (StringUtils.isNotBlank(pagesPackage)) { // Append period after package // packageName = 'org.apache.click.' packageName = pagesPackage + "."; } String className = ""; // Strip off extension. // path = '/pages/edit-customer' String path = pagePath.substring(0, pagePath.lastIndexOf(".")); // If page is excluded return the excluded class Class excludePageClass = getExcludesPageClass(path); if (excludePageClass != null) { return excludePageClass; } // Build complete packageName. // packageName = 'org.apache.click.pages.' // className = 'edit-customer' if (path.indexOf("/") != -1) { StringTokenizer tokenizer = new StringTokenizer(path, "/"); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (tokenizer.hasMoreTokens()) { packageName = packageName + token + "."; } else { className = token; } } } else { className = path; } // CamelCase className. // className = 'EditCustomer' StringTokenizer tokenizer = new StringTokenizer(className, "_-"); className = ""; while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); token = Character.toUpperCase(token.charAt(0)) + token.substring(1); className += token; } // className = 'org.apache.click.pages.EditCustomer' className = packageName + className; Class pageClass = null; try { // Attempt to load class. pageClass = ClickUtils.classForName(className); if (!Page.class.isAssignableFrom(pageClass)) { String msg = "Automapped page class " + className + " is not a subclass of org.apache.click.Page"; throw new RuntimeException(msg); } } catch (ClassNotFoundException cnfe) { boolean classFound = false; // Append "Page" to className and attempt to load class again. // className = 'org.apache.click.pages.EditCustomerPage' if (!className.endsWith("Page")) { String classNameWithPage = className + "Page"; try { // Attempt to load class. pageClass = ClickUtils.classForName(classNameWithPage); if (!Page.class.isAssignableFrom(pageClass)) { String msg = "Automapped page class " + classNameWithPage + " is not a subclass of org.apache.click.Page"; throw new RuntimeException(msg); } classFound = true; } catch (ClassNotFoundException cnfe2) { } } if (!classFound) { if (logService.isDebugEnabled()) { logService.debug(pagePath + " -> CLASS NOT FOUND"); } if (logService.isTraceEnabled()) { logService.trace("class not found: " + className); } } } return pageClass; } /** * Returns true if Click resources (JavaScript, CSS, images etc) packaged * in jars can be deployed to the root directory of the webapp, false * otherwise. * <p/> * By default this method will return false in restricted environments where * write access to the underlying file system is disallowed. Example * environments where write access is not allowed include the WebLogic JEE * server and Google App Engine. (Note: WebLogic provides the property * <tt>"Archived Real Path Enabled"</tt> that controls whether web * applications can access the file system or not. See the Click user manual * for details). * * @return true if resources can be deployed, false otherwise */ protected boolean isResourcesDeployable() { // Only deploy if writes are allowed if (onGoogleAppEngine) { // Google doesn't allow writes return false; } return ClickUtils.isResourcesDeployable(servletContext); } // ------------------------------------------------ Package Private Methods /** * Loads all Click Pages defined in the <tt>click.xml</tt> file, including * manually defined Pages, auto mapped Pages and excluded Pages. * * @param rootElm the root xml element containing the configuration * @throws java.lang.ClassNotFoundException if the specified Page class can * not be found on the classpath */ void loadPages(Element rootElm) throws ClassNotFoundException { List pagesList = ClickUtils.getChildren(rootElm, "pages"); if (pagesList.isEmpty()) { String msg = "required configuration 'pages' element missing."; throw new RuntimeException(msg); } List templates = getTemplateFiles(); for (int i = 0; i < pagesList.size(); i++) { Element pagesElm = (Element) pagesList.get(i); // Determine whether to use automapping boolean automap = true; String automapStr = pagesElm.getAttribute("automapping"); if (StringUtils.isBlank(automapStr)) { automapStr = "true"; } if ("true".equalsIgnoreCase(automapStr)) { automap = true; } else if ("false".equalsIgnoreCase(automapStr)) { automap = false; } else { String msg = "Invalid pages automapping attribute: " + automapStr; throw new RuntimeException(msg); } // Determine whether to use autobinding. String autobindingStr = pagesElm.getAttribute("autobinding"); if (StringUtils.isBlank(autobindingStr)) { autobinding = AutoBinding.DEFAULT; } else { if ("annotation".equalsIgnoreCase(autobindingStr)) { autobinding = AutoBinding.ANNOTATION; } else if ("public".equalsIgnoreCase(autobindingStr)) { autobinding = AutoBinding.DEFAULT; } else if ("default".equalsIgnoreCase(autobindingStr)) { autobinding = AutoBinding.DEFAULT; } else if ("none".equalsIgnoreCase(autobindingStr)) { autobinding = AutoBinding.NONE; // Provided for backward compatibility } else if ("true".equalsIgnoreCase(autobindingStr)) { autobinding = AutoBinding.DEFAULT; // Provided for backward compatibility } else if ("false".equalsIgnoreCase(autobindingStr)) { autobinding = AutoBinding.NONE; } else { String msg = "Invalid pages autobinding attribute: " + autobindingStr; throw new RuntimeException(msg); } } // TODO: if autobinding is set to false an there are multiple pages how should this be handled // Perhaps autobinding should be moved to <click-app> and be a application wide setting? // However the way its implemented above is probably fine for backward compatibility // purposes, meaning the last defined autobinding wins String pagesPackage = pagesElm.getAttribute("package"); if (StringUtils.isBlank(pagesPackage)) { pagesPackage = ""; } pagesPackage = pagesPackage.trim(); if (pagesPackage.endsWith(".") && pagesPackage.length() > 1) { pagesPackage = pagesPackage.substring(0, pagesPackage.length() - 2); } // Add the pages package to the list of page packages pagePackages.add(pagesPackage); buildManualPageMapping(pagesElm, pagesPackage); if (automap) { buildAutoPageMapping(pagesElm, pagesPackage, templates); } } buildClassMap(); } /** * Add manually defined Pages to the {@link #pageByPathMap}. * * @param pagesElm the xml element containing manually defined Pages * @param pagesPackage the pages package prefix * * @throws java.lang.ClassNotFoundException if the specified Page class can * not be found on the classpath */ void buildManualPageMapping(Element pagesElm, String pagesPackage) throws ClassNotFoundException { List pageList = ClickUtils.getChildren(pagesElm, "page"); if (!pageList.isEmpty() && logService.isDebugEnabled()) { logService.debug("click.xml pages:"); } for (int i = 0; i < pageList.size(); i++) { Element pageElm = (Element) pageList.get(i); XmlConfigService.PageElm page = new XmlConfigService.PageElm(pageElm, pagesPackage, commonHeaders, autobinding); pageByPathMap.put(page.getPath(), page); if (logService.isDebugEnabled()) { String msg = page.getPath() + " -> " + page.getPageClass().getName(); logService.debug(msg); } } } /** * Build the {@link #pageByPathMap} by associating template files with * matching Java classes found on the classpath. * <p/> * This method also rebuilds the {@link #excludesList}. This list contains * URL paths that should not be auto-mapped. * * @param pagesElm the xml element containing the excluded URL paths * @param pagesPackage the pages package prefix * @param templates the list of templates to map to Page classes */ void buildAutoPageMapping(Element pagesElm, String pagesPackage, List templates) throws ClassNotFoundException { // Build list of automap path page class overrides excludesList.clear(); for (Iterator i = ClickUtils.getChildren(pagesElm, "excludes").iterator(); i.hasNext();) { excludesList.add(new XmlConfigService.ExcludesElm((Element) i.next())); } if (logService.isDebugEnabled()) { logService.debug("automapped pages:"); } for (int i = 0; i < templates.size(); i++) { String pagePath = (String) templates.get(i); if (!pageByPathMap.containsKey(pagePath)) { Class pageClass = getPageClass(pagePath, pagesPackage); if (pageClass != null) { XmlConfigService.PageElm page = new XmlConfigService.PageElm(pagePath, pageClass, commonHeaders, autobinding); pageByPathMap.put(page.getPath(), page); if (logService.isDebugEnabled()) { String msg = pagePath + " -> " + pageClass.getName(); logService.debug(msg); } } } } } /** * Build the {@link #pageByClassMap} from the {@link #pageByPathMap} and * delegate to {@link #addToClassMap(PageElm)}. */ void buildClassMap() { // Build pages by class map for (Iterator i = pageByPathMap.values().iterator(); i.hasNext();) { XmlConfigService.PageElm page = (XmlConfigService.PageElm) i.next(); addToClassMap(page); } } /** * Add the specified page to the {@link #pageByClassMap} where the Map's key * holds the Page class and value holds the {@link PageElm}. * * @param page the PageElm containing metadata about a specific page */ void addToClassMap(PageElm page) { Object value = pageByClassMap.get(page.pageClass); if (value == null) { pageByClassMap.put(page.pageClass, page); } else if (value instanceof List) { ((List) value).add(page); } else if (value instanceof XmlConfigService.PageElm) { List list = new ArrayList(); list.add(value); list.add(page); pageByClassMap.put(page.pageClass, list); } else { // should never occur throw new IllegalStateException(); } } /** * Load the Page headers from the specified xml element. * * @param parentElm the element to load the headers from * @return the map of Page headers */ static Map loadHeadersMap(Element parentElm) { Map headersMap = new HashMap(); List headerList = ClickUtils.getChildren(parentElm, "header"); for (int i = 0, size = headerList.size(); i < size; i++) { Element header = (Element) headerList.get(i); String name = header.getAttribute("name"); String type = header.getAttribute("type"); String propertyValue = header.getAttribute("value"); Object value = null; if ("".equals(type) || "String".equalsIgnoreCase(type)) { value = propertyValue; } else if ("Integer".equalsIgnoreCase(type)) { value = Integer.valueOf(propertyValue); } else if ("Date".equalsIgnoreCase(type)) { value = new Date(Long.parseLong(propertyValue)); } else { value = null; String message = "Invalid property type [String|Integer|Date]: " + type; throw new IllegalArgumentException(message); } headersMap.put(name, value); } return headersMap; } // -------------------------------------------------------- Private Methods private Element getResourceRootElement(String path) throws IOException { Document document = null; InputStream inputStream = null; try { inputStream = ClickUtils.getResourceAsStream(path, getClass()); if (inputStream != null) { document = ClickUtils.buildDocument(inputStream, this); } } finally { ClickUtils.close(inputStream); } if (document != null) { return document.getDocumentElement(); } else { return null; } } private void deployControls(Element rootElm) throws Exception { if (rootElm == null) { return; } Element controlsElm = ClickUtils.getChild(rootElm, "controls"); if (controlsElm == null) { return; } List deployableList = ClickUtils.getChildren(controlsElm, "control"); for (int i = 0; i < deployableList.size(); i++) { Element deployableElm = (Element) deployableList.get(i); String classname = deployableElm.getAttribute("classname"); if (StringUtils.isBlank(classname)) { String msg = "'control' element missing 'classname' attribute."; throw new RuntimeException(msg); } Class deployClass = ClickUtils.classForName(classname); Control control = (Control) deployClass.newInstance(); control.onDeploy(servletContext); } } private void deployControlSets(Element rootElm) throws Exception { if (rootElm == null) { return; } Element controlsElm = ClickUtils.getChild(rootElm, "controls"); if (controlsElm == null) { return; } List controlSets = ClickUtils.getChildren(controlsElm, "control-set"); for (int i = 0; i < controlSets.size(); i++) { Element controlSet = (Element) controlSets.get(i); String name = controlSet.getAttribute("name"); if (StringUtils.isBlank(name)) { String msg = "'control-set' element missing 'name' attribute."; throw new RuntimeException(msg); } deployControls(getResourceRootElement("/" + name)); } } /** * Deploy files from jars and Controls. * * @param rootElm the click.xml configuration DOM element * @throws java.lang.Exception if files cannot be deployed */ private void deployFiles(Element rootElm) throws Exception { boolean isResourcesDeployable = isResourcesDeployable(); if (isResourcesDeployable) { if (getLogService().isTraceEnabled()) { String deployTarget = servletContext.getRealPath("/"); getLogService().trace("resource deploy folder: " + deployTarget); } deployControls(getResourceRootElement("/click-controls.xml")); deployControls(getResourceRootElement("/extras-controls.xml")); deployControls(rootElm); deployControlSets(rootElm); deployResourcesOnClasspath(); } if (!isResourcesDeployable) { HtmlStringBuffer buffer = new HtmlStringBuffer(); if (onGoogleAppEngine) { buffer.append("Google App Engine does not support deploying"); buffer.append(" resources to the 'click' web folder.\n"); } else { buffer.append("could not deploy Click resources to the 'click'"); buffer.append(" web folder.\nThis can occur if the call to"); buffer.append(" ServletContext.getRealPath(\"/\") returns null, which means"); buffer.append(" the web application cannot determine the file system path"); buffer.append(" to deploy files to. This issue also occurs if the web"); buffer.append(" application is not allowed to write to the file"); buffer.append(" system.\n"); } buffer.append("To resolve this issue please see the Click user-guide:"); buffer.append(" http://click.apache.org/docs/user-guide/html/ch05s03.html#deploying-restricted-env"); buffer.append(" \nIgnore this warning once you have settled on a"); buffer.append(" deployment strategy"); getLogService().warn(buffer.toString()); } } /** * Deploy from the classpath all resources found under the directory * 'META-INF/resources/'. For backwards compatibility resources under the * directory 'META-INF/web/' are also deployed. * <p/> * Only jars and folders available on the classpath are scanned. * * @throws IOException if the resources cannot be deployed */ private void deployResourcesOnClasspath() throws IOException { long startTime = System.currentTimeMillis(); // Find all jars and directories on the classpath that contains the // directory "META-INF/resources/", and deploy those resources String resourceDirectory = "META-INF/resources"; List<String> resources = new DeployUtils(logService).findResources(resourceDirectory).getResources(); for (String resource : resources) { deployFile(resource, resourceDirectory); } // For backward compatibility, find all jars and directories on the // classpath that contains the directory "META-INF/web/", and deploy those // resources resourceDirectory = "META-INF/web"; resources = new DeployUtils(logService).findResources(resourceDirectory).getResources(); for (String resource : resources) { deployFile(resource, resourceDirectory); } logService.trace( "deployed files from jars and folders - " + (System.currentTimeMillis() - startTime) + " ms"); } /** * Deploy the specified file. * * @param file the file to deploy * @param prefix the file prefix that must be removed when the file is * deployed */ private void deployFile(String file, String prefix) { // Only deploy resources containing the prefix int pathIndex = file.indexOf(prefix); if (pathIndex == 0) { pathIndex += prefix.length(); // By default deploy to the web root dir String targetDir = ""; // resourceName example -> click/table.css String resourceName = file.substring(pathIndex); int index = resourceName.lastIndexOf('/'); if (index != -1) { // targetDir example -> click targetDir = resourceName.substring(0, index); } // Copy resources to web folder ClickUtils.deployFile(servletContext, file, targetDir); } } private void loadMode(Element rootElm) { Element modeElm = ClickUtils.getChild(rootElm, "mode"); String modeValue = "development"; if (modeElm != null) { if (StringUtils.isNotBlank(modeElm.getAttribute("value"))) { modeValue = modeElm.getAttribute("value"); } } modeValue = System.getProperty("click.mode", modeValue); if (modeValue.equalsIgnoreCase("production")) { mode = PRODUCTION; } else if (modeValue.equalsIgnoreCase("profile")) { mode = PROFILE; } else if (modeValue.equalsIgnoreCase("development")) { mode = DEVELOPMENT; } else if (modeValue.equalsIgnoreCase("debug")) { mode = DEBUG; } else if (modeValue.equalsIgnoreCase("trace")) { mode = TRACE; } else { logService.error( "invalid application mode: '" + modeValue + "' - defaulted to '" + MODE_VALUES[DEBUG] + "'"); mode = DEBUG; } // Set log levels if (logService instanceof ConsoleLogService) { int logLevel = ConsoleLogService.INFO_LEVEL; if (mode == PRODUCTION) { logLevel = ConsoleLogService.WARN_LEVEL; } else if (mode == DEVELOPMENT) { } else if (mode == DEBUG) { logLevel = ConsoleLogService.DEBUG_LEVEL; } else if (mode == TRACE) { logLevel = ConsoleLogService.TRACE_LEVEL; } ((ConsoleLogService) logService).setLevel(logLevel); } } private void loadDefaultPages() throws ClassNotFoundException { if (!pageByPathMap.containsKey(ERROR_PATH)) { XmlConfigService.PageElm page = new XmlConfigService.PageElm("org.apache.click.util.ErrorPage", ERROR_PATH); pageByPathMap.put(ERROR_PATH, page); } if (!pageByPathMap.containsKey(NOT_FOUND_PATH)) { XmlConfigService.PageElm page = new XmlConfigService.PageElm("org.apache.click.Page", NOT_FOUND_PATH); pageByPathMap.put(NOT_FOUND_PATH, page); } } private void loadHeaders(Element rootElm) { Element headersElm = ClickUtils.getChild(rootElm, "headers"); if (headersElm != null) { commonHeaders = Collections.unmodifiableMap(loadHeadersMap(headersElm)); } else { commonHeaders = Collections.unmodifiableMap(DEFAULT_HEADERS); } } private void loadFormatClass(Element rootElm) throws ClassNotFoundException { Element formatElm = ClickUtils.getChild(rootElm, "format"); if (formatElm != null) { String classname = formatElm.getAttribute("classname"); if (classname == null) { String msg = "'format' element missing 'classname' attribute."; throw new RuntimeException(msg); } formatClass = ClickUtils.classForName(classname); } else { formatClass = org.apache.click.util.Format.class; } } private void loadFileUploadService(Element rootElm) throws Exception { Element fileUploadServiceElm = ClickUtils.getChild(rootElm, "file-upload-service"); if (fileUploadServiceElm != null) { Class fileUploadServiceClass = CommonsFileUploadService.class; String classname = fileUploadServiceElm.getAttribute("classname"); if (StringUtils.isNotBlank(classname)) { fileUploadServiceClass = ClickUtils.classForName(classname); } fileUploadService = (FileUploadService) fileUploadServiceClass.newInstance(); Map propertyMap = loadPropertyMap(fileUploadServiceElm); for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { String name = i.next().toString(); String value = propertyMap.get(name).toString(); Ognl.setValue(name, fileUploadService, value); } } else { fileUploadService = new CommonsFileUploadService(); } if (getLogService().isDebugEnabled()) { String msg = "initializing FileLoadService: " + fileUploadService.getClass().getName(); getLogService().debug(msg); } fileUploadService.onInit(servletContext); } private void loadLogService(Element rootElm) throws Exception { Element logServiceElm = ClickUtils.getChild(rootElm, "log-service"); if (logServiceElm != null) { Class logServiceClass = ConsoleLogService.class; String classname = logServiceElm.getAttribute("classname"); if (StringUtils.isNotBlank(classname)) { logServiceClass = ClickUtils.classForName(classname); } logService = (LogService) logServiceClass.newInstance(); Map propertyMap = loadPropertyMap(logServiceElm); for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { String name = i.next().toString(); String value = propertyMap.get(name).toString(); Ognl.setValue(name, logService, value); } } else { logService = new ConsoleLogService(); } logService.onInit(getServletContext()); } private void loadMessagesMapService(Element rootElm) throws Exception { Element messagesMapServiceElm = ClickUtils.getChild(rootElm, "messages-map-service"); if (messagesMapServiceElm != null) { Class messagesMapServiceClass = DefaultMessagesMapService.class; String classname = messagesMapServiceElm.getAttribute("classname"); if (StringUtils.isNotBlank(classname)) { messagesMapServiceClass = ClickUtils.classForName(classname); } messagesMapService = (MessagesMapService) messagesMapServiceClass.newInstance(); Map propertyMap = loadPropertyMap(messagesMapServiceElm); for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { String name = i.next().toString(); String value = propertyMap.get(name).toString(); Ognl.setValue(name, messagesMapService, value); } } if (getLogService().isDebugEnabled()) { String msg = "initializing MessagesMapService: " + messagesMapService.getClass().getName(); getLogService().debug(msg); } messagesMapService.onInit(servletContext); } private void loadPageInterceptors(Element rootElm) throws Exception { List<Element> interceptorList = ClickUtils.getChildren(rootElm, "page-interceptor"); for (Element interceptorElm : interceptorList) { String classname = interceptorElm.getAttribute("classname"); String scopeValue = interceptorElm.getAttribute("scope"); boolean applicationScope = "application".equalsIgnoreCase(scopeValue); Class interceptorClass = ClickUtils.classForName(classname); Map propertyMap = loadPropertyMap(interceptorElm); List<Property> propertyList = new ArrayList<Property>(); for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { String name = i.next().toString(); String value = propertyMap.get(name).toString(); propertyList.add(new Property(name, value)); } PageInterceptorConfig pageInterceptorConfig = new PageInterceptorConfig(interceptorClass, applicationScope, propertyList); pageInterceptorConfigList.add(pageInterceptorConfig); } } private void loadResourceService(Element rootElm) throws Exception { Element resourceServiceElm = ClickUtils.getChild(rootElm, "resource-service"); if (resourceServiceElm != null) { Class resourceServiceClass = ClickResourceService.class; String classname = resourceServiceElm.getAttribute("classname"); if (StringUtils.isNotBlank(classname)) { resourceServiceClass = ClickUtils.classForName(classname); } resourceService = (ResourceService) resourceServiceClass.newInstance(); Map propertyMap = loadPropertyMap(resourceServiceElm); for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { String name = i.next().toString(); String value = propertyMap.get(name).toString(); Ognl.setValue(name, resourceService, value); } } else { resourceService = new ClickResourceService(); } if (getLogService().isDebugEnabled()) { String msg = "initializing ResourceService: " + resourceService.getClass().getName(); getLogService().debug(msg); } resourceService.onInit(servletContext); } private void loadTemplateService(Element rootElm) throws Exception { Element templateServiceElm = ClickUtils.getChild(rootElm, "template-service"); if (templateServiceElm != null) { Class templateServiceClass = VelocityTemplateService.class; String classname = templateServiceElm.getAttribute("classname"); if (StringUtils.isNotBlank(classname)) { templateServiceClass = ClickUtils.classForName(classname); } templateService = (TemplateService) templateServiceClass.newInstance(); Map propertyMap = loadPropertyMap(templateServiceElm); for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { String name = i.next().toString(); String value = propertyMap.get(name).toString(); Ognl.setValue(name, templateService, value); } } else { templateService = new VelocityTemplateService(); } if (getLogService().isDebugEnabled()) { String msg = "initializing TemplateService: " + templateService.getClass().getName(); getLogService().debug(msg); } templateService.onInit(servletContext); } private static Map loadPropertyMap(Element parentElm) { Map propertyMap = new HashMap(); List propertyList = ClickUtils.getChildren(parentElm, "property"); for (int i = 0, size = propertyList.size(); i < size; i++) { Element property = (Element) propertyList.get(i); String name = property.getAttribute("name"); String value = property.getAttribute("value"); propertyMap.put(name, value); } return propertyMap; } private void loadCharset(Element rootElm) { String localCharset = rootElm.getAttribute("charset"); if (localCharset != null && localCharset.length() > 0) { this.charset = localCharset; } } private void loadLocale(Element rootElm) { String value = rootElm.getAttribute("locale"); if (value != null && value.length() > 0) { StringTokenizer tokenizer = new StringTokenizer(value, "_"); if (tokenizer.countTokens() == 1) { String language = tokenizer.nextToken(); locale = new Locale(language); } else if (tokenizer.countTokens() == 2) { String language = tokenizer.nextToken(); String country = tokenizer.nextToken(); locale = new Locale(language, country); } } } /** * Return the list of templates within the web application. * * @return list of all templates within the web application */ private List getTemplateFiles() { List fileList = new ArrayList(); Set resources = servletContext.getResourcePaths("/"); if (onGoogleAppEngine) { // resources could be immutable so create copy Set tempResources = new HashSet(); // Load the two GAE preconfigured automapped folders tempResources.addAll(servletContext.getResourcePaths("/page")); tempResources.addAll(servletContext.getResourcePaths("/pages")); tempResources.addAll(resources); // assign copy to resources resources = Collections.unmodifiableSet(tempResources); } // Add all resources within web application for (Iterator i = resources.iterator(); i.hasNext();) { String resource = (String) i.next(); if (isTemplate(resource)) { fileList.add(resource); } else if (resource.endsWith("/")) { if (!resource.equalsIgnoreCase("/WEB-INF/")) { processDirectory(resource, fileList); } } } Collections.sort(fileList); return fileList; } private void processDirectory(String dirPath, List fileList) { Set resources = servletContext.getResourcePaths(dirPath); if (resources != null) { for (Iterator i = resources.iterator(); i.hasNext();) { String resource = (String) i.next(); if (isTemplate(resource)) { fileList.add(resource); } else if (resource.endsWith("/")) { processDirectory(resource, fileList); } } } } private Class<? extends Page> getExcludesPageClass(String path) { for (int i = 0; i < excludesList.size(); i++) { XmlConfigService.ExcludesElm override = (XmlConfigService.ExcludesElm) excludesList.get(i); if (override.isMatch(path)) { return override.getPageClass(); } } return null; } /** * Return an array of bindable fields for the given page class based on * the binding mode. * * @param pageClass the page class * @param mode the binding mode * @return the field array of bindable fields */ private static Field[] getBindablePageFields(Class pageClass, AutoBinding mode) { if (mode == AutoBinding.DEFAULT) { // Get @Bindable fields Map<String, Field> fieldMap = getAnnotatedBindableFields(pageClass); // Add public fields Field[] publicFields = pageClass.getFields(); for (Field field : publicFields) { fieldMap.put(field.getName(), field); } // Copy the field map values into a field list Field[] fieldArray = new Field[fieldMap.size()]; int i = 0; for (Field field : fieldMap.values()) { fieldArray[i++] = field; } return fieldArray; } else if (mode == AutoBinding.ANNOTATION) { Map<String, Field> fieldMap = getAnnotatedBindableFields(pageClass); // Copy the field map values into a field list Field[] fieldArray = new Field[fieldMap.size()]; int i = 0; for (Field field : fieldMap.values()) { fieldArray[i++] = field; } return fieldArray; } else { return new Field[0]; } } /** * Return the fields annotated with the Bindable annotation. * * @param pageClass the page class * @return the map of bindable fields */ private static Map getAnnotatedBindableFields(Class pageClass) { List<Class> pageClassList = new ArrayList<Class>(); pageClassList.add(pageClass); Class parentClass = pageClass.getSuperclass(); while (parentClass != null) { // Include parent classes up to but excluding Page.class if (parentClass.isAssignableFrom(Page.class)) { break; } pageClassList.add(parentClass); parentClass = parentClass.getSuperclass(); } // Reverse class list so parents are processed first, with the // actual page class fields processed last. This will enable the // page classes fields to override parent class fields Collections.reverse(pageClassList); Map<String, Field> fieldMap = new TreeMap<String, Field>(); for (Class aPageClass : pageClassList) { for (Field field : aPageClass.getDeclaredFields()) { if (field.getAnnotation(Bindable.class) != null) { fieldMap.put(field.getName(), field); // If field is not public set accessibility true if (!Modifier.isPublic(field.getModifiers())) { field.setAccessible(true); } } } } return fieldMap; } // ---------------------------------------------------------- Inner Classes /** * Provide an Excluded Page class. * <p/> * <b>PLEASE NOTE</b> this class is <b>not</b> for public use, and can be * ignored. */ public static class ExcludePage extends Page { static final Map HEADERS = new HashMap(); static { HEADERS.put("Cache-Control", "max-age=3600, public"); } /** * @see Page#getHeaders() * * @return the map of HTTP header to be set in the HttpServletResponse */ @Override public Map getHeaders() { return HEADERS; } } static class PageElm { final Map<String, Field> fields; final Field[] fieldArray; final Map headers; final Class<? extends Page> pageClass; final String path; public PageElm(Element element, String pagesPackage, Map commonHeaders, AutoBinding autobinding) throws ClassNotFoundException { // Set headers Map aggregationMap = new HashMap(commonHeaders); Map pageHeaders = loadHeadersMap(element); aggregationMap.putAll(pageHeaders); headers = Collections.unmodifiableMap(aggregationMap); // Set path String pathValue = element.getAttribute("path"); if (pathValue.charAt(0) != '/') { path = "/" + pathValue; } else { path = pathValue; } // Retrieve page classname String classname = element.getAttribute("classname"); if (classname == null) { String msg = "No classname defined for page path " + path; throw new RuntimeException(msg); } Class tmpPageClass = null; String classnameFound = null; try { // First, lookup classname as provided tmpPageClass = ClickUtils.classForName(classname); classnameFound = classname; } catch (ClassNotFoundException cnfe) { if (pagesPackage.trim().length() > 0) { // For backward compatibility prefix classname with package name String prefixedClassname = pagesPackage + "." + classname; try { // CLK-704 // For backward compatibility, lookup classname prefixed with the package name tmpPageClass = ClickUtils.classForName(prefixedClassname); classnameFound = prefixedClassname; } catch (ClassNotFoundException cnfe2) { // Throw original exception which used the given classname String msg = "No class was found for the Page classname: '" + classname + "'."; throw new RuntimeException(msg, cnfe); } } } pageClass = tmpPageClass; if (!Page.class.isAssignableFrom(pageClass)) { String msg = "Page class '" + classnameFound + "' is not a subclass of org.apache.click.Page"; throw new RuntimeException(msg); } fieldArray = XmlConfigService.getBindablePageFields(pageClass, autobinding); fields = new HashMap<String, Field>(); for (Field field : fieldArray) { fields.put(field.getName(), field); } } private PageElm(String path, Class pageClass, Map commonHeaders, AutoBinding mode) { headers = Collections.unmodifiableMap(commonHeaders); this.pageClass = pageClass; this.path = path; fieldArray = getBindablePageFields(pageClass, mode); fields = new HashMap<String, Field>(); for (Field field : fieldArray) { fields.put(field.getName(), field); } } public PageElm(String classname, String path) throws ClassNotFoundException { this.fieldArray = new Field[0]; this.fields = Collections.emptyMap(); this.headers = Collections.emptyMap(); pageClass = ClickUtils.classForName(classname); this.path = path; } public Field[] getFieldArray() { return fieldArray; } public Map<String, Field> getFields() { return fields; } public Map getHeaders() { return headers; } public Class<? extends Page> getPageClass() { return pageClass; } public String getPath() { return path; } } static class ExcludesElm { final Set<String> pathSet = new HashSet<String>(); final Set<String> fileSet = new HashSet<String>(); public ExcludesElm(Element element) throws ClassNotFoundException { String pattern = element.getAttribute("pattern"); if (StringUtils.isNotBlank(pattern)) { StringTokenizer tokenizer = new StringTokenizer(pattern, ", "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.charAt(0) != '/') { token = "/" + token; } int index = token.lastIndexOf("."); if (index != -1) { token = token.substring(0, index); fileSet.add(token); } else { index = token.indexOf("*"); if (index != -1) { token = token.substring(0, index); } pathSet.add(token); } } } } public Class<? extends Page> getPageClass() { return XmlConfigService.ExcludePage.class; } public boolean isMatch(String resourcePath) { if (fileSet.contains(resourcePath)) { return true; } for (String path : pathSet) { if (resourcePath.startsWith(path)) { return true; } } return false; } @Override public String toString() { return getClass().getName() + "[fileSet=" + fileSet + ",pathSet=" + pathSet + "]"; } } static class PageInterceptorConfig { final Class<? extends PageInterceptor> interceptorClass; final boolean applicationScope; final List<Property> properties; PageInterceptor pageInterceptor; PageInterceptorConfig(Class<? extends PageInterceptor> interceptorClass, boolean applicationScope, List<Property> properties) { this.interceptorClass = interceptorClass; this.applicationScope = applicationScope; this.properties = properties; } public PageInterceptor getPageInterceptor() { PageInterceptor listener = null; // If cached interceptor not already created (application scope) // or is scope request then create a new interceptor if (pageInterceptor == null || !applicationScope) { try { listener = interceptorClass.newInstance(); Map ognlContext = new HashMap(); for (Property property : properties) { PropertyUtils.setValueOgnl(listener, property.getName(), property.getValue(), ognlContext); } } catch (Exception e) { throw new RuntimeException(e); } if (applicationScope) { pageInterceptor = listener; } } else { listener = pageInterceptor; } return listener; } } static class Property { final String name; final String value; Property(String name, String value) { this.name = name; this.value = value; } public String getName() { return name; } public String getValue() { return value; } } }