Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src-impl/org/seasar/mayaa/impl/CONST_IMPL.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public interface CONST_IMPL {
String DEBUG = "org.seasar.mayaa.debug";

String CHECK_TIMESTAMP = "checkTimestamp";
String CHECK_TIMESTAMP_INTERVAL = "checkTimestampInterval";
String DEFAULT_SPECIFICATION = "defaultSpecification";
String SUFFIX_SEPARATOR = "suffixSeparator";
String WELCOME_FILE_NAME = "welcomeFileName";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.seasar.mayaa.impl.CONST_IMPL;
import org.seasar.mayaa.impl.NonSerializableParameterAwareImpl;
import org.seasar.mayaa.impl.engine.EngineUtil;
import org.seasar.mayaa.impl.engine.specification.SpecificationImpl;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;
import org.seasar.mayaa.impl.engine.specification.xpath.XPathUtil;
import org.seasar.mayaa.impl.util.StringUtil;
Expand All @@ -54,6 +55,13 @@ public class XPathMatchesInjectionResolver extends NonSerializableParameterAware

private static final CopyToFilter _xpathFilter = new CheckXPathCopyToFilter();

// TODO テンプレートのprefix定義、Mayaaファイルのを反映させる
private static final Namespace XPATH_NAMESPACE;
static {
XPATH_NAMESPACE = SpecificationUtil.createNamespace();
XPATH_NAMESPACE.addPrefixMapping("m", CONST_IMPL.URI_MAYAA);
}

protected CopyToFilter getCopyToFilter() {
return _xpathFilter;
}
Expand Down Expand Up @@ -89,34 +97,54 @@ public SpecificationNode getNode(
throw new IllegalArgumentException();
}

// TODO テンプレートのprefix定義、Mayaaファイルのを反映させる
Namespace namespace = SpecificationUtil.createNamespace();
namespace.addPrefixMapping("m", CONST_IMPL.URI_MAYAA);

// mayaaファイル内のm:xpathを持つすべてのノードを対象とする
Specification spec = SpecificationUtil.findSpecification(original);
List<SpecificationNode> injectNodes = new ArrayList<>();
while (spec != null) {
SpecificationNode mayaa = SpecificationUtil.getMayaaNode(spec);
if (mayaa != null) {
getXPathNodes(mayaa, injectNodes);
}
spec = EngineUtil.getParentSpecification(spec);
if (spec == null) {
// original がどの spec にも属さない場合は injection 不可
return chain.getNode(original);
}

for (Iterator<SpecificationNode> it = injectNodes.iterator(); it.hasNext();) {
SpecificationNode injected = it.next();
String mayaaPath = SpecificationUtil.getAttributeValue(
injected, QM_XPATH);
// injectedをnamespaceとして渡すとfunctionのデフォルトnamesapceが
// URI_MAYAAになってしまうため、デフォルトnamespaceのないものを渡す
if (XPathUtil.matches(original, mayaaPath, namespace)) {
return injected.copyTo(getCopyToFilter());
// spec 階層を上向きに辿り、各 spec の xpath inject ノードを順に試す
Specification cur = spec;
while (cur != null) {
List<SpecificationNode> injectNodes = null;
if (cur instanceof SpecificationImpl) {
injectNodes = ((SpecificationImpl) cur).getCachedXpathInjectNodes();
if (injectNodes == null) {
injectNodes = buildXpathInjectNodes(cur);
((SpecificationImpl) cur).setCachedXpathInjectNodes(injectNodes);
}
} else {
injectNodes = buildXpathInjectNodes(cur);
}
for (Iterator<SpecificationNode> it = injectNodes.iterator(); it.hasNext();) {
SpecificationNode injected = it.next();
String mayaaPath = SpecificationUtil.getAttributeValue(injected, QM_XPATH);
// injectedをnamespaceとして渡すとfunctionのデフォルトnamesapceが
// URI_MAYAAになってしまうため、デフォルトnamespaceのないものを渡す
if (XPathUtil.matches(original, mayaaPath, XPATH_NAMESPACE)) {
return injected.copyTo(getCopyToFilter());
}
}
cur = EngineUtil.getParentSpecification(cur);
}
return chain.getNode(original);
}

/**
* 1つの spec の .mayaa ノードだけを走査し、m:xpath ノードのリストを構築する。
*
* @param spec 対象の spec
* @return m:xpath を持つ injection ノードのリスト(この spec のみ)
*/
protected List<SpecificationNode> buildXpathInjectNodes(Specification spec) {
List<SpecificationNode> nodes = new ArrayList<>();
SpecificationNode mayaa = SpecificationUtil.getMayaaNode(spec);
if (mayaa != null) {
getXPathNodes(mayaa, nodes);
}
return nodes;
}

/**
* .mayaaファイルパース後に呼び出す。m:mayaa直下でないノードにxpath属性がある場合、
* 警告を一度だけ記録する。{@link SpecificationBuilderImpl#afterBuild} から呼ばれる。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@

import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

import org.seasar.mayaa.engine.specification.SpecificationNode;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.mayaa.builder.SpecificationBuilder;
Expand Down Expand Up @@ -48,6 +51,15 @@ public class SpecificationImpl extends ParameterAwareImpl implements Specificati
private boolean _hasSource;
private boolean _deprecated = true;
private int _lastSequenceID;
private volatile long _lastExistsCheckMillis = 0;
private volatile long _existsCheckIntervalMillis = -1L;


/**
* XPath injection ノードキャッシュ(この spec の .mayaa に含まれる m:xpath ノード一覧)。
* {@code deprecate()} 呼び出し時にクリアされ、次のビルド時に再構築される。
*/
private volatile List<SpecificationNode> _xpathInjectNodes;

protected boolean needCheckTimestamp() {
return EngineUtil.getEngineSettingBoolean(CONST_IMPL.CHECK_TIMESTAMP, true);
Expand All @@ -57,6 +69,22 @@ protected boolean isSourceExists() {
return getSource().exists();
}

/**
* ソース存在チェック間隔(ミリ秒)を返します。
* 設定値は初回呼び出し時にのみ取得し、インスタンスフィールドにキャッシュします。
*/
protected long getExistsCheckIntervalMillis() {
if (_existsCheckIntervalMillis < 0) {
String val = EngineUtil.getEngineSetting(CONST_IMPL.CHECK_TIMESTAMP_INTERVAL, "500");
try {
_existsCheckIntervalMillis = Long.parseLong(val);
} catch (NumberFormatException e) {
_existsCheckIntervalMillis = 500L;
}
}
return _existsCheckIntervalMillis;
}

protected SpecificationBuilder getBuilder() {
return ProviderUtil.getSpecificationBuilder();
}
Expand Down Expand Up @@ -94,6 +122,12 @@ public String toString() {
// TODO isDeprecatedの高速化
public boolean isDeprecated() {
if (_deprecated == false) {
long intervalMillis = getExistsCheckIntervalMillis();
long now = System.currentTimeMillis();
if (intervalMillis > 0 && (now - _lastExistsCheckMillis) < intervalMillis) {
return false;
}
_lastExistsCheckMillis = now;
_deprecated = _hasSource != isSourceExists();
Comment on lines 124 to 132
if (_deprecated == false) {
if (_hasSource == false) {
Expand All @@ -117,6 +151,23 @@ public boolean isDeprecated() {
@Override
public void deprecate() {
_deprecated = true;
_xpathInjectNodes = null;
}

/**
* この spec の XPath injection ノードリストを返す。
* まだ構築されていない場合は {@code null} を返す。
*/
public List<SpecificationNode> getCachedXpathInjectNodes() {
return _xpathInjectNodes;
}

/**
* この spec の XPath injection ノードリストをセットする。
* 複数スレッドが同時に呼んでも内容は等価なので、上書きは許容する。
*/
public void setCachedXpathInjectNodes(List<SpecificationNode> nodes) {
_xpathInjectNodes = nodes;
}

public void build() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;

import org.jaxen.Context;
import org.jaxen.ContextSupport;
Expand Down Expand Up @@ -47,6 +48,10 @@ private XPathUtil() {
// no instantiation.
}

/** XPath パターン文字列 → コンパイル済み Pattern のキャッシュ(スレッドセーフ)。 */
private static final ConcurrentHashMap<String, Pattern> _patternCache =
new ConcurrentHashMap<>();

public static boolean matches(SpecificationNode test,
String xpathExpr, Namespace namespace) {
if (StringUtil.isEmpty(xpathExpr)) {
Expand All @@ -65,12 +70,16 @@ public static boolean matches(SpecificationNode test,
SpecificationNavigator.getInstance());
Context context = new Context(support);
try {
Pattern pattern = PatternParser.parse(xpathExpr);
Pattern pattern = _patternCache.computeIfAbsent(xpathExpr, expr -> {
try {
return PatternParser.parse(expr);
} catch (SAXPathException e) {
throw new RuntimeException(e);
}
});
return pattern.matches(test, context);
} catch (JaxenException e) {
throw new RuntimeException(e);
} catch (SAXPathException e) {
throw new RuntimeException(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* 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.functional.engine;

import java.io.IOException;

import org.junit.jupiter.api.Test;
import org.seasar.mayaa.functional.EngineTestBase;
import org.seasar.mayaa.impl.engine.EngineImpl;
import org.seasar.mayaa.impl.provider.ProviderUtil;
import org.springframework.mock.web.MockHttpServletRequest;

/**
* テンプレートビルド(injection フェーズ)の性能測定。
* キャッシュ最適化の効果を確認するための簡易ベンチマーク。
*/
public class BuildPerformanceTest extends EngineTestBase {

private static final int WARMUP = 100;
private static final int ITERATIONS = 2000;

/**
* XPath ベースの injection (XPathMatchesInjectionResolver) の繰り返しビルド時間を計測。
*/
@Test
public void bench_inject_xpath() throws IOException {
final String TARGET = "/it-case/engine/inject_xpath/target.html";
runBench("inject_xpath (XPath cache)", TARGET);
}

/**
* ID ベースの injection (EqualsIDInjectionResolver) の繰り返しビルド時間を計測。
*/
@Test
public void bench_inject_mayaa() throws IOException {
final String TARGET = "/it-case/engine/inject_mayaa/target.html";
runBench("inject_mayaa (ID cache) ", TARGET);
}

/**
* XPath ベースの injection — テンプレートのみ deprecate(.mayaa は再読み込みしない)。
* injection フェーズ単体の性能を計測しやすい。
*/
@Test
public void bench_inject_xpath_templateonly() throws IOException {
final String TARGET = "/it-case/engine/inject_xpath/target.html";
runBench("inject_xpath (template-only) ", TARGET, false);
Comment on lines +36 to +64
}

/**
* ID ベースの injection — テンプレートのみ deprecate(.mayaa は再読み込みしない)。
*/
@Test
public void bench_inject_mayaa_templateonly() throws IOException {
final String TARGET = "/it-case/engine/inject_mayaa/target.html";
runBench("inject_mayaa (template-only)", TARGET, false);
}

// ------------------------------------------------------------------

private void runBench(String label, String target) throws IOException {
runBench(label, target, true, false);
}

private void runBench(String label, String target, boolean deprecateMayaa) throws IOException {
runBench(label, target, deprecateMayaa, false);
}

private void runBench(String label, String target, boolean deprecateMayaa, boolean deprecateDefault) throws IOException {
EngineImpl engine = (EngineImpl) ProviderUtil.getEngine();

// ウォームアップ(JIT 最適化を安定させる)
for (int i = 0; i < WARMUP; i++) {
execAndDeprecate(engine, target, deprecateMayaa, deprecateDefault);
}

// 計測
long start = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
execAndDeprecate(engine, target, deprecateMayaa, deprecateDefault);
}
long elapsed = System.nanoTime() - start;

double totalMs = elapsed / 1_000_000.0;
double perMs = totalMs / ITERATIONS;
System.out.printf("[BENCH] %-38s %d iters total=%.1f ms per=%.3f ms%n",
label, ITERATIONS, totalMs, perMs);
}

private void execAndDeprecate(EngineImpl engine, String path, boolean deprecateMayaa, boolean deprecateDefault) {
MockHttpServletRequest request = createRequest(path);
exec(request, null);
// キャッシュを無効化して毎回ビルドを走らせる
engine.deprecateSpecification(path);
if (deprecateMayaa) {
// .mayaa も無効化する(injection ルールの再読み込みを強制)
String mayaaPath = path.replaceAll("\\.[^.]+$", ".mayaa");
engine.deprecateSpecification(mayaaPath);
}
if (deprecateDefault) {
// default.mayaa も無効化する(全スペック再構築を強制)
engine.deprecateSpecification("/default.mayaa");
}
}
}
Loading