/*
* 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.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.seasar.mayaa.builder.SpecificationBuilder;
import org.seasar.mayaa.cycle.Response;
import org.seasar.mayaa.cycle.ServiceCycle;
import org.seasar.mayaa.cycle.scope.RequestScope;
import org.seasar.mayaa.engine.Page;
import org.seasar.mayaa.engine.Template;
import org.seasar.mayaa.engine.processor.ProcessStatus;
import org.seasar.mayaa.engine.processor.ProcessorTreeWalker;
import org.seasar.mayaa.engine.specification.QName;
import org.seasar.mayaa.engine.specification.Specification;
import org.seasar.mayaa.engine.specification.SpecificationNode;
import org.seasar.mayaa.engine.specification.serialize.ProcessorReferenceResolver;
import org.seasar.mayaa.engine.specification.serialize.ProcessorResolveListener;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.cycle.DefaultCycleLocalInstantiator;
import org.seasar.mayaa.impl.engine.specification.SpecificationImpl;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;
import org.seasar.mayaa.impl.engine.specification.serialize.ProcessorSerializeController;
import org.seasar.mayaa.impl.provider.ProviderUtil;
import org.seasar.mayaa.impl.util.ObjectUtil;
import org.seasar.mayaa.impl.util.StringUtil;
/**
* @author Masataka Kurihara (Gluegent, Inc.)
*/
public class TemplateImpl
extends SpecificationImpl
implements Template, ProcessorReferenceResolver {
private static final long serialVersionUID = 2126209350220642842L;
private String _pageName;
private String _suffix;
private String _extension;
private List _childProcessors = new ArrayList();
public void initialize(Page page, String suffix, String extension) {
if (page == null || suffix == null || extension == null) {
throw new IllegalArgumentException();
}
if (_pageName != null) {
// only once
throw new IllegalStateException();
}
_pageName = page.getPageName();
_suffix = suffix;
_extension = extension;
}
public Page getPage() {
return ProviderUtil.getEngine().getPage(_pageName);
}
public String getSuffix() {
return _suffix;
}
public String getExtension() {
return _extension;
}
protected String getMayaaAttribute(Specification spec, QName qname) {
SpecificationNode mayaa = SpecificationUtil.getMayaaNode(spec);
if (mayaa != null) {
return SpecificationUtil.getAttributeValue(mayaa, qname);
}
return null;
}
protected String findMayaaAttribute(Page topLevelPage, QName qname) {
String topLevelValue = getMayaaAttribute(topLevelPage, qname);
if (StringUtil.hasValue(topLevelValue)) {
return topLevelValue;
}
Specification spec = this;
while (spec != null) {
String value = getMayaaAttribute(spec, qname);
if (StringUtil.hasValue(value)) {
return value;
}
spec = EngineUtil.getParentSpecification(spec);
}
return null;
}
/**
* m:mayaaタグのcontentType属性の値を取得します。
* 静的な文字列として処理します。
* topLevelPageに属性が無い場合は親mayaaを辿って探します。
* MayaaファイルにcontentType属性がなく、テンプレートにmetaタグでcontent-type
* が定義されている場合、ビルド時にMayaaのcontentType属性にコピーされます。
*
* @param topLevelPage
* @return contentType属性の値、またはnull
*/
protected String getContentType(Page topLevelPage) {
String contentType = findMayaaAttribute(topLevelPage, QM_CONTENT_TYPE);
if (contentType != null) {
return contentType;
}
// default
RequestScope request = CycleUtil.getRequestScope();
String ret = request.getMimeType();
if (ret == null) {
ret = "text/html;charset=" + TEMPLATE_DEFAULT_CHARSET;
} else if (ret.indexOf("charset") == -1) {
ret = ret + ";charset=" + TEMPLATE_DEFAULT_CHARSET;
}
return ret;
}
/**
* m:mayaaタグのnoCache属性の値を取得します。
* 静的な文字列として処理します。
* topLevelPageに属性が無い場合は親mayaaを辿って探します。
*
* @param topLevelPage
* @return noCache属性の値、またはnull
*/
protected boolean isNoCache(Page topLevelPage) {
String noCache = findMayaaAttribute(topLevelPage, QM_NO_CACHE);
if (noCache != null) {
return ObjectUtil.booleanValue(noCache, false);
}
return false;
}
/**
* m:mayaaタグのcacheControl属性の値を取得します。
* 静的な文字列として処理します。
* topLevelPageに属性が無い場合は親mayaaを辿って探します。
*
* @param topLevelPage
* @return cacheControl属性の値、またはnull
*/
protected String getCacheControl(Page topLevelPage) {
return findMayaaAttribute(topLevelPage, QM_CACHE_CONTROL);
}
/**
* content-typeの設定、cache-controlの設定などレスポンスの前処理をします。
* noCache属性がセットされている場合、下記3つのヘッダが設定されます。
* <ul><li>Pragma: no-cache</li>
* <li>Cache-Control: no-cache</li>
* <li>Expires: Thu, 01 Dec 1994 16:00:00 GMT</li>
* </ul>
* cacheControl属性がセットされている場合、その値がCache-Controlヘッダの
* 値として設定されます。
* noCache属性とcacheControl属性の両方がセットされている場合、Cache-Control
* ヘッダの値はcacheControl属性の値が設定されます。
*
* @param topLevelPage
*/
protected void prepareCycle(Page topLevelPage) {
ServiceCycle cycle = CycleUtil.getServiceCycle();
Response response = cycle.getResponse();
String contentType = getContentType(topLevelPage);
String cacheControl = getCacheControl(topLevelPage);
response.setContentType(contentType);
if (isNoCache(topLevelPage)) {
response.addHeader("Pragma", "no-cache");
if (cacheControl == null) {
response.addHeader("Cache-Control",
EngineUtil.getEngineSetting(EngineImpl.NO_CACHE_VALUE, "no-cache"));
}
response.addHeader("Expires", "Thu, 01 Dec 1994 16:00:00 GMT");
}
if (cacheControl != null) {
response.addHeader("Cache-Control", cacheControl);
}
}
public ProcessStatus doTemplateRender(Page topLevelPage) {
RenderUtil.saveToCycle(this);
prepareCycle(topLevelPage);
ProcessStatus ret =
RenderUtil.renderProcessorTree(topLevelPage, this);
return ret;
}
protected void replaceProcessors(List processors) {
synchronized (this) {
clearChildProcessors();
for (int i = 0; i < processors.size(); i++) {
Object item = processors.get(i);
if (item instanceof ProcessorTreeWalker) {
addChildProcessor((ProcessorTreeWalker)processors.get(i));
}
}
}
}
public void build() {
build(true);
}
public void build(boolean rebuild) {
CycleUtil.beginDraftWriting();
try {
super.build(rebuild);
} finally {
CycleUtil.endDraftWriting();
}
}
protected SpecificationBuilder getBuilder() {
return ProviderUtil.getTemplateBuilder();
}
public boolean isDeprecated() {
Date templateTime = getTimestamp();
if (templateTime != null) {
Date pageTime = getPage().getTimestamp();
if (pageTime != null && pageTime.after(templateTime)) {
return true;
}
Date engineTime = ProviderUtil.getEngine().getTimestamp();
if (engineTime != null && engineTime.after(templateTime)) {
return true;
}
}
return super.isDeprecated();
}
// ProcessorTreeWalker implements --------------------------------
public Map getVariables() {
return null;
}
public void setParentProcessor(ProcessorTreeWalker parent) {
throw new IllegalStateException();
}
public ProcessorTreeWalker getParentProcessor() {
return null;
}
public ProcessorTreeWalker getStaticParentProcessor() {
return null;
}
public void addChildProcessor(ProcessorTreeWalker child) {
insertProcessor(_childProcessors.size(), child);
}
public void insertProcessor(int index, ProcessorTreeWalker child) {
synchronized (_childProcessors) {
if (child == null) {
throw new IllegalArgumentException();
}
if (index < 0 || index > _childProcessors.size()) {
throw new IndexOutOfBoundsException();
}
_childProcessors.add(index, child);
child.setParentProcessor(this);
for (index += 1; index < _childProcessors.size(); index++) {
child = (ProcessorTreeWalker)_childProcessors.get(index);
child.setParentProcessor(this);
}
}
}
public boolean removeProcessor(ProcessorTreeWalker child) {
synchronized (this) {
return _childProcessors.remove(child);
}
}
public void clearChildProcessors() {
synchronized (this) {
_childProcessors.clear();
}
}
public int getChildProcessorSize() {
return _childProcessors.size();
}
public ProcessorTreeWalker getChildProcessor(int index) {
return (ProcessorTreeWalker) _childProcessors.get(index);
}
// for serialize
private static final String SERIALIZE_CONTROLLER_KEY =
TemplateImpl.class.getName() + "#serializeController";
static {
CycleUtil.registVariableFactory(SERIALIZE_CONTROLLER_KEY,
new DefaultCycleLocalInstantiator() {
public Object create(Object[] params) {
ProcessorSerializeController result =
new ProcessorSerializeController();
result.init();
return result;
}
});
}
private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
out.defaultWriteObject();
}
private void readObject(java.io.ObjectInputStream in)
throws java.io.IOException, ClassNotFoundException {
in.defaultReadObject();
if (_childProcessors != null) {
for (int i = _childProcessors.size() - 1; i >= 0 ; i--) {
ProcessorTreeWalker child =
(ProcessorTreeWalker) _childProcessors.get(i);
child.setParentProcessor(this);
}
}
nodeSerializer().specLoaded(this);
}
protected void afterDeserialize() {
processorSerializer().release();
}
public static ProcessorSerializeController processorSerializer() {
return (ProcessorSerializeController) CycleUtil.getGlobalVariable(
SERIALIZE_CONTROLLER_KEY, null);
}
// ProcessorReferenceResolver implements ------------------------
public void registResolveProcessorListener(
String uniqueID, ProcessorResolveListener listener) {
processorSerializer().registResolveProcessorListener(uniqueID, listener);
}
public void processorLoaded(String uniqueID, ProcessorTreeWalker item) {
processorSerializer().processorLoaded(uniqueID, item);
}
public ProcessorReferenceResolver findProcessorResolver() {
return processorSerializer();
}
// PositionAware implements ------------------------------------
public boolean isOnTemplate() {
return true;
}
}