/*
* 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.standalone;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.mayaa.FactoryFactory;
import org.seasar.mayaa.engine.Engine;
import org.seasar.mayaa.engine.Page;
import org.seasar.mayaa.engine.specification.Specification;
import org.seasar.mayaa.impl.FactoryFactoryImpl;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.cycle.web.MockHttpServletRequest;
import org.seasar.mayaa.impl.cycle.web.MockHttpServletResponse;
import org.seasar.mayaa.impl.cycle.web.MockServletContext;
import org.seasar.mayaa.impl.engine.EngineImpl;
import org.seasar.mayaa.impl.engine.processor.JspProcessor;
import org.seasar.mayaa.impl.engine.specification.serialize.SerializeThreadManager;
import org.seasar.mayaa.impl.provider.ProviderUtil;
import org.seasar.mayaa.impl.source.ApplicationSourceDescriptor;
import org.seasar.mayaa.impl.source.SourceHolderFactory;
import org.seasar.mayaa.impl.util.ReferenceCache;
import org.seasar.mayaa.impl.util.StringUtil;
import org.seasar.mayaa.source.SourceHolder;
/**
* コンテキストルートからレンダリング対象のファイルを探し、レンダリングした結果を
* ファイルに書き出します。
* コンテキストパスはROOTの扱い(空文字列)になります。
*
* 必要な情報はbasePathとoutputPathです。
* basePathはWebアプリケーションでいうところのコンテキストルートで、レンダリング
* するファイルを探すルートフォルダです。
* 存在しない場合はIllegalArgumentExceptionが発生します。
* outputPathはレンダリング結果を出力する先のフォルダ名です。
* 存在しない場合は自動的に作成します。
*
* また、AutoPageBuilderと同様の形式でファイル名フィルタを指定できます。
* フィルタを指定しない場合は".html"の拡張子を持つファイルが対象になります。
* フィルタの指定方法は2パターンあり、セミコロン(";")で区切ることで複数指定できます。
* <ol>
* <li>"."で始まる英数字のみの文字列の場合は拡張子とみなし、一致するものを対象とします。
* (大文字小文字を区別しない)</li>
* <li>1以外の場合は正規表現とみなし、絶対パスがマッチするものを対象とします。</li>
* </ol>
*
* @author Koji Suga (Gluegent Inc.)
*/
public class FileSearchRenderer {
private static final Log LOG = LogFactory.getLog(FileSearchRenderer.class);
private static final String OPTION_AUTO_BUILD_FILE_FILTERS =
"autoBuild.fileNameFilters";
private static final String CONTEXT_PATH = "";
private String _outputPath;
private MockServletContext _servletContext;
private String[] _fileFilters;
private long _buildTimeSum;
/**
* インスタンスを初期化します。
*
* basePath, outputPath, contextPathは末尾が"/"でないパスを指定してください。
* contextPathには空文字列または"/"始まりの文字列で渡してください。
* ファイル名フィルタにはEngine設定のautoBuildのものを利用します。
*
* @param basePath WebContentにあたるフォルダ。
* @param outputPath 出力先のルートフォルダ。
* @throws IllegalArgumentException basePath, outputPathがnullまたは空文字列の場合,
* basePathが存在しない場合
*/
public void init(final String basePath, final String outputPath) {
Engine engine = ProviderUtil.getEngine();
String filters = engine.getParameter(OPTION_AUTO_BUILD_FILE_FILTERS);
init(basePath, outputPath, filters);
}
/**
* インスタンスを初期化します。
*
* basePath, outputPath, contextPathは末尾が"/"でないパスを指定してください。
* contextPathには空文字列または"/"始まりの文字列で渡してください。
* ファイル名フィルタがnullまたは空文字列の場合、".html"が適用されます。
*
* @param basePath WebContentにあたるフォルダ。
* @param outputPath 出力先のルートフォルダ。
* @param filters ファイル名フィルタ
* @throws IllegalArgumentException basePath, outputPathがnullまたは空文字列の場合,
* basePathが存在しない場合
*/
public void init(
final String basePath, final String outputPath, final String filters) {
if (StringUtil.isEmpty(basePath)) {
throw new IllegalArgumentException("basePath cannot be null.");
}
if (StringUtil.isEmpty(outputPath)) {
throw new IllegalArgumentException("outputPath cannot be null.");
}
String absoluteBasePath = preparePath(basePath);
File base = new File(absoluteBasePath);
if (base.exists() == false) {
throw new IllegalArgumentException(absoluteBasePath + " is not exists.");
} else if (base.isDirectory() == false) {
throw new IllegalArgumentException(absoluteBasePath + " is not directory.");
}
_servletContext = new MockServletContext(absoluteBasePath, CONTEXT_PATH);
_outputPath = preparePath(outputPath);
if (StringUtil.hasValue(filters)) {
_fileFilters = filters.split(";");
} else {
_fileFilters = new String[]{".html"};
}
LOG.info("init start");
FactoryFactory.setInstance(new FactoryFactoryImpl());
FactoryFactory.setContext(_servletContext);
LOG.info("prepareLibraries start");
ProviderUtil.getLibraryManager().prepareLibraries();
LOG.info("prepareLibraries end");
// 不要な設定を強制的に止める
Engine engine = ProviderUtil.getEngine();
engine.setParameter(EngineImpl.SURVIVE_LIMIT, "1");
engine.setParameter(EngineImpl.PAGE_SERIALIZE, "false");
LOG.info("init end");
}
/**
* パスの前処理として、"\"を"/"に置き換え、末尾の"/"を削って返します。
* パスが"."で始まっている場合、カレントのパスからの相対パスと見なします。
*
* @param targetPath 対象のパス文字列
* @return 処理済みのパス文字列
*/
protected String preparePath(final String targetPath) {
if (StringUtil.isEmpty(targetPath)) {
return targetPath;
}
String path = targetPath.replace(File.separatorChar, '/');
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
if (path.startsWith(".")) {
File pathFile = new File(System.getProperty("user.dir") + "/" + path);
try {
path = pathFile.getCanonicalPath();
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
return path;
}
/**
* ServletContextに属性を追加します。
* キーがStringのもののみ追加対象となります。
*
* @param attributes 属性のマップ
*/
public void addAttributes(final Map attributes) {
if (attributes != null && attributes.size() > 0) {
for (Iterator keys = attributes.keySet().iterator(); keys.hasNext();) {
Object key = keys.next();
if (key instanceof String) {
_servletContext.setAttribute((String) key, attributes.get(key));
}
}
}
}
/**
* ファイルへのレンダリングを開始します。
* ファイルを探し、filtersに適合するものを順番にレンダリングしていきます。
*/
public void start() {
Engine engine = ProviderUtil.getEngine();
long engineBuildTime = diffMillis(0);
engine.build();
reportTime(engine, diffMillis(engineBuildTime));
_buildTimeSum = 0;
for (Iterator it = SourceHolderFactory.iterator(); it.hasNext();) {
SourceHolder holder = (SourceHolder) it.next();
for (Iterator itSystemID = holder.iterator(_fileFilters);
itSystemID.hasNext();) {
String systemID = (String) itSystemID.next();
try {
buildPage(systemID);
} catch (Throwable e) {
LOG.error("page load failed: " + systemID, e);
}
}
}
LOG.info("page all build time: " + _buildTimeSum + " msec.");
}
public void destroy() {
ReferenceCache.finishThreads();
ProviderUtil.getEngine().destroy();
SerializeThreadManager.destroy();
JspProcessor.clear();
LogFactory.releaseAll();
}
protected void reportTime(final Specification spec, final long time) {
if (LOG.isDebugEnabled()) {
LOG.debug(spec.getSystemID() + " build time: " + time + " msec.");
}
}
protected long diffMillis(final long millis) {
if (millis == 0) {
return System.currentTimeMillis();
}
return System.currentTimeMillis() - millis;
}
/**
* レンダリング結果を出力するOutputStreamを作成します。
*
* @param request HttpServletRequest
* @param systemID レンダリング対象のsystemID
* @return レンダリング結果を出力するOutputStream
*/
protected OutputStream createOutputStream(
final MockHttpServletRequest request, final String systemID) {
String preparedSystemID;
if (systemID.startsWith("/") == false) {
preparedSystemID = "/" + systemID;
} else {
preparedSystemID = systemID;
}
final String filePath =
_outputPath + preparedSystemID.replace('/', File.separatorChar);
new File(filePath).getParentFile().mkdirs();
try {
LOG.info("output: " + filePath);
return new FileOutputStream(filePath);
} catch (FileNotFoundException e) {
LOG.info(e.getMessage());
return null;
}
}
/**
* ページをビルドしてレンダリングし、結果をファイルに出力します。
*
* @param systemID 対象ページのSystemID
*/
protected void buildPage(final String systemID) {
if (systemID.indexOf(ApplicationSourceDescriptor.WEB_INF) >= 0) {
return;
}
final MockHttpServletRequest request =
new MockHttpServletRequest(_servletContext, CONTEXT_PATH);
final MockHttpServletResponse response = new MockHttpServletResponse();
// 結果をファイルに出力するようOutputStreamをセットする
response.setOnCommitOutputStream(createOutputStream(request, systemID));
request.setPathInfo(systemID);
CycleUtil.initialize(request, response);
try {
final String pageName = CycleUtil.getRequestScope().getPageName();
Engine engine = ProviderUtil.getEngine();
if (engine.isPageRequested()) {
long pageBuildTime = diffMillis(0);
final Page page = engine.getPage(pageName);
pageBuildTime = diffMillis(pageBuildTime);
reportTime(page, pageBuildTime);
_buildTimeSum += pageBuildTime;
long templateBuildTime = diffMillis(0);
page.getTemplate(
CycleUtil.getRequestScope().getRequestedSuffix(),
CycleUtil.getRequestScope().getExtension());
templateBuildTime = diffMillis(templateBuildTime);
_buildTimeSum += templateBuildTime;
long renderTime = diffMillis(0);
engine.doService(null, true);
renderTime = diffMillis(renderTime);
_buildTimeSum += renderTime;
if (LOG.isDebugEnabled()) {
if (response.getStatus() ==
HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {
LOG.debug(systemID + " (render: ----"
+ " / build: " + templateBuildTime + " msec)");
} else {
LOG.debug(systemID + " (render: " + renderTime
+ " / build: " + templateBuildTime + " msec)");
}
}
}
} finally {
CycleUtil.cycleFinalize();
}
}
/**
* @return the buildTimeSum
*/
public long getBuildTimeSum() {
return _buildTimeSum;
}
/**
* @param buildTimeSum the buildTimeSum to set
*/
public void setBuildTimeSum(long buildTimeSum) {
_buildTimeSum = buildTimeSum;
}
/**
* fileFiltersの浅いコピーを取得します。
*
* @return the fileFilters
*/
public String[] getFileFilters() {
return StringUtil.arraycopy(_fileFilters);
}
/**
* fileFiltersに浅いコピーをセットします。
*
* @param fileFilters the fileFilters to set
* @throws NullPointerException
*/
public void setFileFilters(String[] fileFilters) {
_fileFilters = StringUtil.arraycopy(fileFilters);
}
/**
* @return the outputPath
*/
public String getOutputPath() {
return _outputPath;
}
/**
* @param outputPath the outputPath to set
*/
public void setOutputPath(String outputPath) {
_outputPath = outputPath;
}
/**
* @return the servletContext
*/
public MockServletContext getServletContext() {
return _servletContext;
}
/**
* @param servletContext the servletContext to set
*/
public void setServletContext(MockServletContext servletContext) {
_servletContext = servletContext;
}
}