/*
* Copyright 2008-2010 the T2 Project ant the Others.
*
* Licensed 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.t2framework.confeito.contexts.impl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.t2framework.confeito.adapter.ContainerAdapter;
import org.t2framework.confeito.contexts.Application;
import org.t2framework.confeito.contexts.Chain;
import org.t2framework.confeito.contexts.Request;
import org.t2framework.confeito.contexts.Response;
import org.t2framework.confeito.contexts.Session;
import org.t2framework.confeito.contexts.WebApplication;
import org.t2framework.confeito.contexts.WebConfiguration;
import org.t2framework.confeito.contexts.WebContext;
import org.t2framework.confeito.exception.NoSuchComponentException;
import org.t2framework.confeito.internal.PageUtil;
import org.t2framework.confeito.model.Component;
import org.t2framework.confeito.model.PageComponent;
import org.t2framework.confeito.spi.AnnotationResolverCreator;
import org.t2framework.confeito.spi.PageRegistrationCommand;
import org.t2framework.confeito.spi.PageRegistrationHandler;
import org.t2framework.confeito.spi.impl.DefaultPageRegistrationHandlerImpl;
import org.t2framework.confeito.urltemplate.UrlTemplate;
import org.t2framework.confeito.util.Assertion;
import org.t2framework.confeito.util.Logger;
import org.t2framework.confeito.util.Traversal;
import org.t2framework.confeito.util.Reflections.ClassUtil;
import org.t2framework.confeito.util.Reflections.PackageUtil;
/**
* <#if locale="en">
* <p>
* An implementation of {@link WebApplication} and Traversal.ClassHandler.
* {@link WebApplicationImpl} takes care of registration page classes at
* initalizing time.Basically, {@link WebApplicationImpl} traverses file system,
* jar file, and zip file to collect all of page classes.To customize behavior,
* you can create your own {@link PageRegistrationHandler}.
*
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*
* @author shot
* @author yone098
* @see org.t2framework.confeito.contexts.WebApplication
*/
public class WebApplicationImpl implements WebApplication,
Traversal.ClassHandler {
/**
* <#if locale="en">
* <p>
* The class name as logging key.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected static final String CLASS_NAME = WebApplicationImpl.class
.getName();
/**
* <#if locale="en">
* <p>
* The logger for debugging.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected static Logger logger = Logger.getLogger(CLASS_NAME);
/**
* <#if locale="en">
* <p>
* The logger for registration succeed.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected static Logger success = Logger.getLogger(CLASS_NAME + ".success");
/**
* <#if locale="en">
* <p>
* The logger for ignoring classes.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected static Logger ignored = Logger.getLogger(CLASS_NAME + ".ignored");
/**
* <#if locale="en">
* <p>
* The {@link ContainerAdapter} for creating {@link PageComponent}
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected ContainerAdapter<?> adapter;
/**
* <#if locale="en">
* <p>
* The root packages.It must not be null.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected String[] rootPackages;
/**
* <#if locale="en">
* <p>
* Eagerload option.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected boolean eagerload;
/**
* <#if locale="en">
* <p>
* Concurrent map of {@link PageComponent}(key: class name of page, value :
* {@link PageComponent} instance).
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected ConcurrentHashMap<String, PageComponent> pageDescMap = new ConcurrentHashMap<String, PageComponent>();
/**
* <#if locale="en">
* <p>
* Temporal class cache for creating page instance.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected Map<Class<?>, UrlTemplate> classCache = new ConcurrentHashMap<Class<?>, UrlTemplate>();
/**
* <#if locale="en">
* <p>
* Annotation resolver creator.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected final AnnotationResolverCreator resolverCreator;
/**
* <#if locale="en">
* <p>
* list of {@link PageRegistrationHandler}.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
protected List<PageRegistrationHandler> handlers = new ArrayList<PageRegistrationHandler>();
public WebApplicationImpl(WebConfiguration config) {
this(config.getContainerAdapter(), config.getRootPackages(), config
.getEagerLoad(), config.getResolverCreator());
}
public WebApplicationImpl(ContainerAdapter<?> adapter,
final String[] rootPackages, final boolean eagerload,
final AnnotationResolverCreator resolverCreator) {
this.adapter = Assertion.notNull(adapter, "adapter");
this.rootPackages = Assertion.notNull(rootPackages);
this.eagerload = eagerload;
this.resolverCreator = Assertion.notNull(resolverCreator);
}
/**
* <#if locale="en">
* <p>
* Set up all strategies that it has, and begin traversing by root packages.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
@Override
public void initialize() {
setupPageRegistrationHandler();
traverse();
}
protected void setupPageRegistrationHandler() {
List<PageRegistrationHandler> customHandlers = this.adapter
.getComponents(PageRegistrationHandler.class);
if (customHandlers != null && customHandlers.isEmpty() == false) {
this.handlers.addAll(customHandlers);
}
this.handlers.add(new DefaultPageRegistrationHandlerImpl(this));
}
protected synchronized void traverse() {
logger.log("ITDT0026");
long start = System.currentTimeMillis();
for (String rootPackage : rootPackages) {
for (PageRegistrationHandler handler : this.handlers) {
PageRegistrationCommand ret = handler.handle(rootPackage,
classCache);
if (ret != PageRegistrationCommand.CONTINUE) {
break;
}
}
}
registerIfEagerload();
final long registerationTime = System.currentTimeMillis() - start;
logger.log("ITDT0027", new Object[] { new Long(registerationTime) });
}
protected void registerIfEagerload() {
if (eagerload) {
final ContainerAdapter<?> adapter = getContainerAdapter();
for (Class<?> c : this.classCache.keySet()) {
logger.log("ITDT0033", new Object[] { c.getName() });
adapter.register(c);
}
}
}
/**
* <#if locale="en">
* <p>
* Create {@link WebContext}.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
@Override
public WebContext createContext(final HttpServletRequest req,
final HttpServletResponse res, final FilterChain filterChain,
final FilterConfig config) {
final Request request = createRequest(req, res);
final Response response = createResponse(res);
final Session session = createSession(request);
final Application application = createApplication(config);
final Chain chain = createChain(filterChain);
ContextImpl context = createContext(request, response, application,
session, chain);
context.setContainerAdapter(getContainerAdapter());
WebContext.set(context);
return context;
}
protected ContextImpl createContext(Request request, Response response,
Application application, Session session, Chain chain) {
return new ContextImpl(request, response, application, session, chain);
}
protected Chain createChain(FilterChain filterChain) {
return new ChainImpl(filterChain);
}
protected Application createApplication(FilterConfig config) {
return new ApplicationImpl(config.getServletContext(), config);
}
protected Session createSession(final Request request) {
return new SessionImpl(request);
}
protected Request createRequest(final HttpServletRequest req,
final HttpServletResponse res) {
return new RequestImpl(req, res);
}
protected Response createResponse(final HttpServletResponse res) {
return new ResponseImpl(res);
}
@Override
public void processClass(String packageName, String shortClassName) {
logger.log("ITDT0028", new Object[] { packageName, shortClassName });
if (packageName == null || shortClassName == null) {
ignored.log("ITDT0029",
new Object[] { packageName, shortClassName });
return;
}
StringBuilder builder = new StringBuilder();
builder.append(packageName.replace('/', '.'));
builder.append(".");
builder.append(shortClassName);
final String classname = new String(builder);
Class<Object> c = ClassUtil.forNameNoException(classname);
if (c == null) {
ignored.log("ITDT0037",
new Object[] { packageName, shortClassName });
return;
}
if (c.isAnnotation() || c.isEnum()) {
ignored.log("ITDT0030",
new Object[] { packageName, shortClassName });
return;
}
final int mod = c.getModifiers();
if (Modifier.isInterface(mod) || Modifier.isAbstract(mod)
|| Modifier.isPublic(mod) == false) {
ignored.log("ITDT0031",
new Object[] { packageName, shortClassName });
return;
}
processPage(c);
}
protected void processPage(Class<?> cd) {
if (PageUtil.isPageClass(cd) == false) {
return;
}
final UrlTemplate template = PageUtil.toUrlTemplate(cd);
final String className = cd.getName();
if (template != null) {
success.log("ITDT0034", new Object[] { className });
classCache.put(cd, template);
} else {
ignored.log("ITDT0032", new Object[] { className });
}
}
protected ContainerAdapter<?> getContainerAdapter() {
return adapter;
}
/**
* <#if locale="en">
* <p>
* Create map of url path to {@link PageComponent}.If the map is empty, it
* will try to load all {@link PageComponent}.
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
@Override
public Map<String, PageComponent> createPageDescMap() {
if (pageDescMap.isEmpty()) {
loadPageDescMap();
}
return pageDescMap;
}
@SuppressWarnings( { "unchecked", "rawtypes" })
protected void loadPageDescMap() {
for (Iterator<Class<?>> itr = classCache.keySet().iterator(); itr
.hasNext();) {
Class key = itr.next();
UrlTemplate template = classCache.get(key);
final Component component = adapter.getBeanDesc(key);
if (component == null) {
throw new NoSuchComponentException(key.getName());
}
final String packageName = PackageUtil.getPackageName(key);
final String simpleClassName = ClassUtil.getSimpleClassName(key);
final Set<Class<? extends Annotation>> actionAnnotationSet = resolverCreator
.getActionAnnotationSet(adapter);
final PageComponent pd = new PageComponent(packageName,
simpleClassName, component, template, actionAnnotationSet);
pageDescMap.putIfAbsent(key.getName(), pd);
}
}
@Override
public String[] getRootPackages() {
return rootPackages;
}
/**
* <#if locale="en">
* <p>
* {@inheritDoc}
* </p>
* <#else>
* <p>
*
* </p>
* </#if>
*/
@Override
public void destroy() {
this.adapter = null;
this.rootPackages = null;
this.eagerload = false;
this.pageDescMap.clear();
this.classCache.clear();
this.handlers.clear();
}
@Override
public Map<Class<?>, UrlTemplate> getClassCache() {
return this.classCache;
}
}