/* * 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; } }