blob: 0a617535549c860f6f054a5362789d30a2648bc1 [file] [log] [blame]
/*
* 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 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.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: &nbsp; "<tt>org.apache.click</tt>". */
static final String CLICK_LOGGER = "org.apache.click";
/** The click deployment directory path: &nbsp; "/click". */
static final String CLICK_PATH = "/click";
/** The default common page headers. */
static final Map<String, Object> DEFAULT_HEADERS;
/**
* The default velocity properties filename: &nbsp;
* "<tt>/WEB-INF/velocity.properties</tt>".
*/
static final String DEFAULT_VEL_PROPS = "/WEB-INF/velocity.properties";
/** The click DTD file name: &nbsp; "<tt>click.dtd</tt>". */
static final String DTD_FILE_NAME = "click.dtd";
/**
* The resource path of the click DTD file: &nbsp;
* "<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: &nbsp; "<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: &nbsp; "<tt>org.apache.velocity</tt>".
*/
static final String VELOCITY_LOGGER = "org.apache.velocity";
/**
* The global Velocity macro file name: &nbsp;
* "<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 PropertyService. */
private PropertyService propertyService;
/** 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 Property service
loadPropertyService(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 (getPropertyService() != null) {
getPropertyService().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: &nbsp; <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#getProperyService()
*
* @return the application property service.
*/
public PropertyService getPropertyService() {
// TODO
return new OGNLPropertyService();
}
/**
* @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">
* &lt;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"&gt;
*
* &lt;!-- Specify a custom ConfigService through the context param 'config-service-class' --&gt;
* &lt;context-param&gt;
* &lt;param-name&gt;config-service-class&lt;/param-name&gt;
* &lt;param-value&gt;com.mycorp.service.MyConfigSerivce&lt;/param-value&gt;
* &lt;/context-param&gt;
*
* &lt;servlet&gt;
* &lt;servlet-name&gt;ClickServlet&lt;/servlet-name&gt;
* &lt;servlet-class&gt;org.apache.click.ClickServlet&lt;/servlet-class&gt;
* &lt;load-on-startup&gt;0&lt;/load-on-startup&gt;
* &lt;/servlet&gt;
*
* &lt;!-- NOTE: we still map the .htm extension --&gt;
* &lt;servlet-mapping&gt;
* &lt;servlet-name&gt;ClickServlet&lt;/servlet-name&gt;
* &lt;url-pattern&gt;*.htm&lt;/url-pattern&gt;
* &lt;/servlet-mapping&gt;
*
* &lt;!-- NOTE: we also map .xml extension in order to route xml requests to the ClickServlet --&gt;
* &lt;servlet-mapping&gt;
* &lt;servlet-name&gt;ClickServlet&lt;/servlet-name&gt;
* &lt;url-pattern&gt;*.xml&lt;/url-pattern&gt;
* &lt;/servlet-mapping&gt;
*
* ...
*
* &lt;/web-app&gt; </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<Class<? extends Page>> getPageClassList() {
List<Class<? extends Page>> classList =
new ArrayList<Class<? extends Page>>(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<? extends Page> 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<Element> pagesList = ClickUtils.getChildren(rootElm, "pages");
if (pagesList.isEmpty()) {
String msg = "required configuration 'pages' element missing.";
throw new RuntimeException(msg);
}
List templates = getTemplateFiles();
for (Element pagesElm : pagesList) {
// 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<String, Object> loadHeadersMap(Element parentElm) {
Map<String, Object> headersMap = new HashMap<String, Object>();
for (Element header : ClickUtils.getChildren(parentElm, "header")) {
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;
}
for (Element deployableElm : ClickUtils.getChildren(controlsElm, "control")) {
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;
}
for (Element controlSet : ClickUtils.getChildren(controlsElm, "control-set")) {
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<String, String> propertyMap = loadPropertyMap(fileUploadServiceElm);
for (String name : propertyMap.keySet()) {
String value = propertyMap.get(name).toString();
getPropertyService().setValue(fileUploadService, name, 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<String, String> propertyMap = loadPropertyMap(logServiceElm);
for (String name : propertyMap.keySet()) {
String value = propertyMap.get(name).toString();
getPropertyService().setValue(logService, name, 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<String, String> propertyMap = loadPropertyMap(messagesMapServiceElm);
for (String name : propertyMap.keySet()) {
String value = propertyMap.get(name).toString();
getPropertyService().setValue(messagesMapService, name, 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<String, String> propertyMap = loadPropertyMap(interceptorElm);
List<Property> propertyList = new ArrayList<Property>();
for (String name : propertyMap.keySet()) {
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<String, String> propertyMap = loadPropertyMap(resourceServiceElm);
for (String name : propertyMap.keySet()) {
String value = propertyMap.get(name).toString();
getPropertyService().setValue(resourceService, name, value);
}
} else {
resourceService = new ClickResourceService();
}
if (getLogService().isDebugEnabled()) {
String msg = "initializing ResourceService: "
+ resourceService.getClass().getName();
getLogService().debug(msg);
}
resourceService.onInit(servletContext);
}
private void loadPropertyService(Element rootElm) throws Exception {
Element propertyServiceElm = ClickUtils.getChild(rootElm, "property-service");
if (propertyServiceElm != null) {
Class propertyServiceClass = OGNLPropertyService.class;
String classname = propertyServiceElm.getAttribute("classname");
if (StringUtils.isNotBlank(classname)) {
propertyServiceClass = ClickUtils.classForName(classname);
}
propertyService = (PropertyService) propertyServiceClass.newInstance();
} else {
propertyService = new OGNLPropertyService();
}
if (getLogService().isDebugEnabled()) {
String msg = "initializing PropertyService: "
+ propertyService.getClass().getName();
getLogService().debug(msg);
}
propertyService.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<String, String> propertyMap = loadPropertyMap(templateServiceElm);
for (String name : propertyMap.keySet()) {
String value = propertyMap.get(name).toString();
getPropertyService().setValue(templateService, name, value);
}
} else {
templateService = new VelocityTemplateService();
}
if (getLogService().isDebugEnabled()) {
String msg = "initializing TemplateService: "
+ templateService.getClass().getName();
getLogService().debug(msg);
}
templateService.onInit(servletContext);
}
private static Map<String, String> loadPropertyMap(Element parentElm) {
Map<String, String> propertyMap = new HashMap<String, String>();
for (Element property : ClickUtils.getChildren(parentElm, "property")) {
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);
}
} else {
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();
ConfigService configService = ClickUtils.getConfigService();
PropertyService propertyService = configService.getPropertyService();
for (Property property : properties) {
propertyService.setValue(listener,
property.getName(),
property.getValue());
}
} 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;
}
}
}