Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -15,10 +15,14 @@
*/
package org.seasar.mayaa.impl.engine.specification;

import java.util.Collections;
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 @@ -46,8 +50,17 @@ public class SpecificationImpl extends ParameterAwareImpl implements Specificati
private transient SourceDescriptor _source;
private volatile NodeTreeWalkerImpl _delegateNodeTreeWalker;
private boolean _hasSource;
private boolean _deprecated = true;
private volatile 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 +70,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 +123,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 +152,24 @@ 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 = Collections.unmodifiableList(nodes);
}

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

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

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

/** XPath パターン文字列 → コンパイル済み Pattern のキャッシュ(スレッドセーフ)。
* XPath 式はアプリ起動後に原則変わらないが、最大 10,000 エントリを上限とし
* メモリ圧迫を防ぐ。 */
private static final Cache<String, Pattern> _patternCache =
Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build();

public static boolean matches(SpecificationNode test,
String xpathExpr, Namespace namespace) {
if (StringUtil.isEmpty(xpathExpr)) {
Expand All @@ -65,12 +78,16 @@ public static boolean matches(SpecificationNode test,
SpecificationNavigator.getInstance());
Context context = new Context(support);
try {
Pattern pattern = PatternParser.parse(xpathExpr);
Pattern pattern = _patternCache.get(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,122 @@
/*
* 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.Tag;
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 フェーズ)の性能測定。
* キャッシュ最適化の効果を確認するための簡易ベンチマーク。
* <p>
* 通常の CI では実行されない。{@code mvn test -P withPerformanceTest} で実行する。
*/
@Tag("test.PerformanceTest")
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