Java tutorial
/* * Copyright 2004-2012 the Seasar Foundation and 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.seasar.mayaa.impl.engine; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.DateFormat; import java.text.ParseException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.seasar.mayaa.cycle.Response; import org.seasar.mayaa.cycle.ServiceCycle; import org.seasar.mayaa.cycle.scope.RequestScope; import org.seasar.mayaa.engine.Engine; import org.seasar.mayaa.engine.Page; import org.seasar.mayaa.engine.Template; import org.seasar.mayaa.engine.error.ErrorHandler; import org.seasar.mayaa.engine.processor.ProcessStatus; import org.seasar.mayaa.engine.specification.NodeTreeWalker; import org.seasar.mayaa.engine.specification.Specification; import org.seasar.mayaa.impl.cycle.CycleUtil; import org.seasar.mayaa.impl.engine.specification.SpecificationImpl; import org.seasar.mayaa.impl.engine.specification.SpecificationUtil; import org.seasar.mayaa.impl.source.DelaySourceDescriptor; import org.seasar.mayaa.impl.source.SourceUtil; import org.seasar.mayaa.impl.util.DateFormatPool; import org.seasar.mayaa.impl.util.IOUtil; import org.seasar.mayaa.impl.util.ObjectUtil; import org.seasar.mayaa.impl.util.StringUtil; import org.seasar.mayaa.source.SourceDescriptor; /** * @author Masataka Kurihara (Gluegent, Inc) */ public class EngineImpl extends SpecificationImpl implements Engine { private static final long serialVersionUID = 643873507172011552L; static final Log LOG = LogFactory.getLog(EngineImpl.class); public static final String PAGE_CLASS = "pageClass"; public static final String TEMPLATE_CLASS = "templateClass"; public static final String PAGE_SERIALIZE = "pageSerialize"; public static final String SURVIVE_LIMIT = "surviveLimit"; public static final String FORWARD_LIMIT = "forwardLimit"; public static final String REQUESTED_SUFFIX_ENABLED = "requestedSuffixEnabled"; public static final String DUMP_ENABLED = "dumpEnabled"; public static final String CONVERT_CHARSET = "convertCharset"; public static final String NO_CACHE_VALUE = "noCacheValue"; private static final boolean DEFAULT_PAGE_SERIALIZE = true; private transient Specification _defaultSpecification; private transient ErrorHandler _errorHandler; private transient SpecificationCache _specCache; /** Engine?????true????? */ private volatile boolean _destroyed = false; // parameters private String _defaultSpecificationID = "/default.mayaa"; private List _templatePathPatterns; private Class _pageClass = PageImpl.class; private Class _templateClass = TemplateImpl.class; private int _surviveLimit = 5; private boolean _requestedSuffixEnabled = false; private boolean _dumpEnabled = false; private int _forwardLimit = 10; private String _mayaaExtension = ".mayaa"; private String _noCacheValue = "no-cache"; // change on setParameter private String _mayaaExtensionName = "mayaa"; private String _defaultPageName = "/default"; private boolean _defaultIsMayaa = false; public EngineImpl() { setSpecificationSerialize(DEFAULT_PAGE_SERIALIZE); } public void setErrorHandler(ErrorHandler errorHandler) { _errorHandler = errorHandler; } public ErrorHandler getErrorHandler() { if (_errorHandler == null) { throw new IllegalStateException(); } return _errorHandler; } public Specification findSpecificationFromCache(String systemID) { return getCache().get(systemID); } protected Page findPageFromCache(String pageName) { return (Page) findSpecificationFromCache(pageName + _mayaaExtension); } public Page getPage(String pageName) { if (_defaultIsMayaa && _defaultPageName.equals(pageName)) { if (LOG.isDebugEnabled()) { LOG.debug(StringUtil.getMessage(EngineImpl.class, 2)); } RequestScope request = CycleUtil.getRequestScope(); throw new PageNotFoundException(pageName, request.getExtension()); } Page page = findPageFromCache(pageName); if (page == null) { page = createPageInstance(pageName); } return page; } public boolean isPageRequested() { RequestScope request = CycleUtil.getRequestScope(); if (_mayaaExtensionName.equals(request.getExtension())) { return true; } return validPath(request.getRequestedPath(), request.getMimeType()); } private boolean validPath(String path, String mimeType) { if (_templatePathPatterns != null) { for (Iterator it = _templatePathPatterns.iterator(); it.hasNext();) { PathPattern pattern = (PathPattern) it.next(); if (pattern.matches(path)) { return pattern.isTemplate(); } } } return mimeType != null && (mimeType.indexOf("html") != -1 || mimeType.indexOf("xml") != -1); } protected Throwable removeWrapperRuntimeException(Throwable t) { Throwable throwable = t; while (throwable.getClass().equals(RuntimeException.class)) { if (throwable.getCause() == null) { break; } throwable = throwable.getCause(); } return throwable; } public void handleError(Throwable thrown, boolean pageFlush) { Throwable throwable = removeWrapperRuntimeException(thrown); if (EngineUtil.isClientAbortException(throwable)) { // client abort ???????? return; } if (throwable instanceof CyclicForwardException) { // forward??????? throw (CyclicForwardException) throwable; } ServiceCycle cycle = CycleUtil.getServiceCycle(); try { cycle.setHandledError(throwable); getErrorHandler().doErrorHandle(throwable, pageFlush); } catch (RenderingTerminated ignore) { // do nothing. } catch (PageForwarded pf) { // return page service checkCyclicForward(cycle.getRequestScope()); doPageService(cycle, null, pageFlush); } catch (Throwable internalThrown) { Throwable internal = removeWrapperRuntimeException(internalThrown); if (internal instanceof PageNotFoundException) { if (LOG.isInfoEnabled()) { LOG.info(throwable.getMessage()); } } else { if (EngineUtil.isClientAbortException(internal)) { // client abort ???????? return; } if (LOG.isFatalEnabled()) { String fatalMsg = StringUtil.getMessage(EngineImpl.class, 0, internal.getMessage()); LOG.fatal(fatalMsg, internal); } } if (throwable instanceof RuntimeException) { throw (RuntimeException) throwable; } throw new RuntimeException(throwable); } finally { cycle.setHandledError(null); } } protected void checkCyclicForward(RequestScope request) { if (_forwardLimit <= 0) { return; } String key = CyclicForwardException.class.getName(); Map pathMap = (Map) request.getAttribute(key); if (pathMap == null) { pathMap = new HashMap(); request.setAttribute(key, pathMap); } String pageName = getRequestedPageName(request); Integer count = (Integer) pathMap.get(pageName); int countInt = (count != null) ? count.intValue() + 1 : 1; if (countInt > _forwardLimit) { throw new CyclicForwardException(pageName); } pathMap.put(pageName, new Integer(countInt)); } protected String getRequestedPageName(RequestScope request) { StringBuffer sb = new StringBuffer(); sb.append(request.getPageName()); if (StringUtil.hasValue(request.getRequestedSuffix())) { sb.append(getSuffixSeparator()); sb.append(request.getRequestedSuffix()); } sb.append('.'); sb.append(request.getExtension()); return sb.toString(); } protected void saveToCycle() { ServiceCycle cycle = CycleUtil.getServiceCycle(); cycle.setOriginalNode(this); cycle.setInjectedNode(this); } public void build() { build(true); } public void build(boolean rebuild) { synchronized (this) { if (_defaultSpecification != null) { if (_defaultSpecification.isDeprecated() == false) { return; } releaseDefaultSpecification(); } if (StringUtil.hasValue(_defaultSpecificationID)) { _defaultSpecification = createDefaultSpecification(_defaultSpecificationID); } } } // NodeTreeWalker override public void addChildNode(NodeTreeWalker childNode) { if (_defaultSpecification != null) { _defaultSpecification.addChildNode(childNode); return; } throw new UnsupportedOperationException(); } public NodeTreeWalker getChildNode(int index) { if (_defaultSpecification != null) { return _defaultSpecification.getChildNode(index); } throw new UnsupportedOperationException(); } public int getChildNodeSize() { if (_defaultSpecification != null) { return _defaultSpecification.getChildNodeSize(); } throw new UnsupportedOperationException(); } public void clearChildNodes() { if (_defaultSpecification != null) { _defaultSpecification.clearChildNodes(); } throw new UnsupportedOperationException(); } protected void releaseDefaultSpecification() { _defaultSpecification = null; } public void insertChildNode(int index, NodeTreeWalker childNode) { if (_defaultSpecification != null) { _defaultSpecification.insertChildNode(index, childNode); } throw new UnsupportedOperationException(); } public boolean removeChildNode(NodeTreeWalker node) { if (_defaultSpecification != null) { _defaultSpecification.removeChildNode(node); } throw new UnsupportedOperationException(); } public Iterator iterateChildNode() { if (_defaultSpecification != null) { return _defaultSpecification.iterateChildNode(); } throw new UnsupportedOperationException(); } public Date getTimestamp() { if (_defaultSpecification != null) { return _defaultSpecification.getTimestamp(); } return null; } public boolean isDeprecated() { if (_defaultSpecification != null) { return _defaultSpecification.isDeprecated(); } return false; } protected void doPageService(ServiceCycle cycle, Map pageScopeValues, boolean pageFlush) { build(); try { boolean service = true; while (service) { try { String pageName = null; String extension = null; String requestedSuffix = null; ProcessStatus ret = null; saveToCycle(); SpecificationUtil.initScope(); SpecificationUtil.startScope(pageScopeValues); try { SpecificationUtil.execEvent(_defaultSpecification, QM_BEFORE_RENDER); RequestScope request = cycle.getRequestScope(); pageName = request.getPageName(); extension = request.getExtension(); if (_requestedSuffixEnabled) { requestedSuffix = request.getRequestedSuffix(); } Page page = getPage(pageName); ret = page.doPageRender(requestedSuffix, extension); saveToCycle(); SpecificationUtil.execEvent(_defaultSpecification, QM_AFTER_RENDER); } finally { SpecificationUtil.endScope(); } Response response = CycleUtil.getResponse(); if (ret == null) { if (response.getWriter().isDirty() == false) { throw new RenderNotCompletedException(pageName, extension); } } if (pageFlush) { response.flush(); } service = false; } catch (RenderingTerminated rt) { service = false; } catch (PageForwarded ignore) { checkCyclicForward(cycle.getRequestScope()); } } } catch (Throwable t) { cycle.getResponse().clearBuffer(); SpecificationUtil.initScope(); handleError(t, pageFlush); saveToCycle(); } } protected void doResourceService(ServiceCycle cycle) { if (cycle == null) { throw new IllegalArgumentException(); } String path = cycle.getRequestScope().getRequestedPath(); SourceDescriptor source = SourceUtil.getSourceDescriptor(path); source.setSystemID(path); String[] modifiedSinces = (String[]) cycle.getRequestScope().getHeaderValues() .getAttribute("If-Modified-Since"); if (modifiedSinces != null && modifiedSinces.length > 0) { String modifiedSince = modifiedSinces[0]; Date sinceDate = null; DateFormat format = DateFormatPool.borrowRFC1123Format(); try { sinceDate = format.parse(modifiedSince); } catch (ParseException e) { DateFormat format2 = DateFormatPool.borrowRFC2822Format(); try { sinceDate = format2.parse(modifiedSince); } catch (ParseException e2) { // give up } finally { DateFormatPool.returnFormat(format2); } } finally { DateFormatPool.returnFormat(format); } Date sourceTimestamp = new Date(source.getTimestamp().getTime() / 1000 * 1000); if (sinceDate != null && sourceTimestamp.after(sinceDate) == false) { cycle.getResponse().setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } } InputStream stream = source.getInputStream(); if (stream != null) { DateFormat format = DateFormatPool.borrowRFC1123Format(); try { cycle.getResponse().setHeader("Last-Modified", format.format(source.getTimestamp())); } finally { DateFormatPool.returnFormat(format); } OutputStream out = cycle.getResponse().getOutputStream(); try { byte[] buffer = new byte[512]; int readln; while ((readln = stream.read(buffer)) != -1) { out.write(buffer, 0, readln); } out.flush(); } catch (IOException e) { if (EngineUtil.isClientAbortException(e)) { stream = null; } else { throw new RuntimeException(e); } } finally { if (stream != null) { IOUtil.close(stream); } } } else { cycle.getResponse().setStatus(HttpServletResponse.SC_NOT_FOUND); } } public void doService(Map pageScopeValues, boolean pageFlush) { if (_destroyed) { throw new IllegalStateException(StringUtil.getMessage(EngineImpl.class, 1)); } ServiceCycle cycle = CycleUtil.getServiceCycle(); if (isPageRequested()) { doPageService(cycle, pageScopeValues, pageFlush); if (_dumpEnabled) { dump(cycle); } } else { doResourceService(cycle); } } protected void dump(ServiceCycle cycle) { ProcessorDump dump = new ProcessorDump(); dump.setPrintContents(true); RequestScope request = cycle.getRequestScope(); String pageName = request.getPageName(); Page page = getPage(pageName); dump.printSource(page); } protected SpecificationCache getCache() { if (_specCache == null) { _specCache = new SpecificationCache(_surviveLimit); } return _specCache; } private static interface SpecificationGenerator { Class getInstantiator(SourceDescriptor source); void initialize(Specification instance); } protected Specification createSpecificationInstance(String systemID, boolean registerCache, SpecificationGenerator generator) { boolean rebuild = false; Specification spec; if (isSpecificationSerialize()) { spec = deserialize(systemID); if (spec != null) { if (spec.isDeprecated() == false) { if (registerCache) { getCache().add(spec); } return spec; } rebuild = true; } } SourceDescriptor source = SourceUtil.getSourceDescriptor(systemID); Class specClass = generator.getInstantiator(source); if (specClass == null) { return null; } spec = (Specification) ObjectUtil.newInstance(specClass); generator.initialize(spec); if (spec instanceof SpecificationImpl) { ((SpecificationImpl) spec).setSpecificationSerialize(isSpecificationSerialize()); } spec.setSource(source); spec.setSystemID(systemID); try { SpecificationUtil.startScope(null); try { spec.build(rebuild); } finally { SpecificationUtil.endScope(); } } catch (RuntimeException e) { throw e; } if (registerCache) { getCache().add(spec); } return spec; } public Specification createDefaultSpecification(final String systemID) { return createSpecificationInstance(systemID, false, new SpecificationGenerator() { public Class getInstantiator(SourceDescriptor source) { return SpecificationImpl.class; } public void initialize(Specification instance) { // no-op } }); } public Page createPageInstance(final String pageName) { return (Page) createSpecificationInstance(pageName + _mayaaExtension, true, new SpecificationGenerator() { public Class getInstantiator(SourceDescriptor source) { return getPageClass(); } public void initialize(Specification instance) { ((Page) instance).initialize(pageName); } }); } public Template createTemplateInstance(final Page page, final String suffix, final String extension) { return (Template) createSpecificationInstance(getTemplateID(page, suffix, extension), true, new SpecificationGenerator() { public Class getInstantiator(SourceDescriptor source) { if (source.exists() == false) { return null; } return getTemplateClass(); } public void initialize(Specification instance) { ((Template) instance).initialize(page, suffix, extension); } }); } public String getTemplateID(Page page, String suffix, String extension) { StringBuffer name = new StringBuffer(page.getPageName()); if (StringUtil.hasValue(suffix)) { String separator = getSuffixSeparator(); name.append(separator).append(suffix); } if (StringUtil.hasValue(extension)) { name.append(".").append(extension); } return name.toString(); } protected String getSuffixSeparator() { return EngineUtil.getEngineSetting(SUFFIX_SEPARATOR, "$"); } public synchronized void destroy() { _destroyed = true; releaseDefaultSpecification(); if (_specCache != null) { _specCache.release(); } } protected void finalize() throws Throwable { destroy(); super.finalize(); } // Parameterizable implements ------------------------------------ public void setParameter(String name, String value) { if (DEFAULT_SPECIFICATION.equals(name)) { if (StringUtil.hasValue(value)) { String systemID; if (value.charAt(0) != '/') { systemID = "/" + value; } else { systemID = value; } SourceDescriptor source = new DelaySourceDescriptor(); source.setSystemID(systemID); setSource(source); _defaultSpecificationID = systemID; setupDefaultIsMayaa(); } } else if (TEMPLATE_PATH_PATTERN.equals(name)) { if (StringUtil.hasValue(value)) { if (_templatePathPatterns == null) { _templatePathPatterns = new LinkedList(); } PathPattern pathPattern = new PathPattern(Pattern.compile(value), true); _templatePathPatterns.add(0, pathPattern); } } else if (NOT_TEMPLATE_PATH_PATTERN.equals(name)) { if (StringUtil.hasValue(value)) { if (_templatePathPatterns == null) { _templatePathPatterns = new LinkedList(); } PathPattern pathPattern = new PathPattern(Pattern.compile(value), false); _templatePathPatterns.add(0, pathPattern); } } else if (PAGE_CLASS.equals(name)) { if (StringUtil.hasValue(value)) { Class pageClass = ObjectUtil.loadClass(value); if (Page.class.isAssignableFrom(pageClass)) { _pageClass = pageClass; } } } else if (MAYAA_EXTENSION.equals(name)) { if (StringUtil.hasValue(value)) { if (value.startsWith(".")) { _mayaaExtension = value; _mayaaExtensionName = value.substring(1); } else { _mayaaExtension = "." + value; _mayaaExtensionName = value; } setupDefaultIsMayaa(); } } else if (TEMPLATE_CLASS.equals(name)) { if (StringUtil.hasValue(value)) { Class templateClass = ObjectUtil.loadClass(value); if (Template.class.isAssignableFrom(templateClass)) { _templateClass = templateClass; } } } else if (NO_CACHE_VALUE.equals(name)) { _noCacheValue = value; } else if (PAGE_SERIALIZE.equals(name)) { setSpecificationSerialize(Boolean.valueOf(value).booleanValue()); } else if (SURVIVE_LIMIT.equals(name)) { _surviveLimit = Integer.parseInt(value); } else if (FORWARD_LIMIT.equals(name)) { _forwardLimit = Integer.parseInt(value); } else if (REQUESTED_SUFFIX_ENABLED.equals(name)) { _requestedSuffixEnabled = Boolean.valueOf(value).booleanValue(); } else if (DUMP_ENABLED.equals(name)) { _dumpEnabled = Boolean.valueOf(value).booleanValue(); } else if (CONVERT_CHARSET.equals(name)) { CharsetConverter.setEnabled(Boolean.valueOf(value).booleanValue()); } super.setParameter(name, value); } private void setupDefaultIsMayaa() { _defaultIsMayaa = _defaultSpecificationID.endsWith(_mayaaExtension); if (_defaultIsMayaa) { _defaultPageName = _defaultSpecificationID.substring(0, _defaultSpecificationID.lastIndexOf('.')); } } public String getParameter(String name) { if (DEFAULT_SPECIFICATION.equals(name)) { return _defaultSpecificationID; } else if (TEMPLATE_PATH_PATTERN.equals(name)) { if (_templatePathPatterns == null) { return null; } return patternToString(_templatePathPatterns, true); } else if (NOT_TEMPLATE_PATH_PATTERN.equals(name)) { if (_templatePathPatterns == null) { return null; } return patternToString(_templatePathPatterns, false); } else if (MAYAA_EXTENSION.equals(name)) { return _mayaaExtension; } else if (PAGE_CLASS.equals(name)) { return _pageClass.getName(); } else if (TEMPLATE_CLASS.equals(name)) { return _templateClass.getName(); } else if (NO_CACHE_VALUE.equals(name)) { return _noCacheValue; } else if (PAGE_SERIALIZE.equals(name)) { return String.valueOf(isSpecificationSerialize()); } else if (SURVIVE_LIMIT.equals(name)) { return String.valueOf(_surviveLimit); } else if (FORWARD_LIMIT.equals(name)) { return String.valueOf(_forwardLimit); } else if (REQUESTED_SUFFIX_ENABLED.equals(name)) { return String.valueOf(_requestedSuffixEnabled); } else if (DUMP_ENABLED.equals(name)) { return String.valueOf(_dumpEnabled); } return super.getParameter(name); } private String patternToString(List patterns, boolean result) { StringBuffer sb = new StringBuffer(); for (Iterator it = patterns.iterator(); it.hasNext();) { PathPattern pathPattern = (PathPattern) it.next(); if (pathPattern.isTemplate() == result) { sb.append(pathPattern.getPattern()); sb.append("|"); } } return sb.toString(); } protected Class getPageClass() { return _pageClass; } protected Class getTemplateClass() { return _templateClass; } //---- support class private static class PathPattern { private Pattern _pattern; private boolean _result; public PathPattern(Pattern pattern, boolean result) { _pattern = pattern; _result = result; } public boolean matches(String path) { return _pattern.matcher(path).matches(); } public boolean isTemplate() { return _result; } public String getPattern() { return _pattern.pattern(); } } }