Skip to content

Commit 0eb9594

Browse files
authored
WEB-INF配下を明示指定でスキャンできるWebInfSourceScannerを新設 (#106)
* WEB-INF内で標準的なタグライブラリ読み込みロジックを備えたWebInfSourceScannerを追加 * ビルトインの ServiceProviderファイルについて既存の定義をWebInfSourceScannerで代替
1 parent ea59424 commit 0eb9594

File tree

6 files changed

+558
-79
lines changed

6 files changed

+558
-79
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
/*
2+
* Copyright 2004-2024 the Seasar Foundation and the Others.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific language
14+
* governing permissions and limitations under the License.
15+
*/
16+
package org.seasar.mayaa.impl.builder.library.scanner;
17+
18+
import java.io.IOException;
19+
import java.nio.file.FileSystems;
20+
import java.nio.file.FileVisitResult;
21+
import java.nio.file.FileVisitor;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.nio.file.PathMatcher;
25+
import java.nio.file.Paths;
26+
import java.nio.file.attribute.BasicFileAttributes;
27+
import java.util.ArrayList;
28+
import java.util.Date;
29+
import java.util.Iterator;
30+
import java.util.List;
31+
import java.util.jar.JarEntry;
32+
import java.util.jar.JarInputStream;
33+
import org.apache.commons.logging.Log;
34+
import org.apache.commons.logging.LogFactory;
35+
import org.seasar.mayaa.builder.library.scanner.SourceScanner;
36+
import org.seasar.mayaa.cycle.scope.ApplicationScope;
37+
import org.seasar.mayaa.impl.NonSerializableParameterAwareImpl;
38+
import org.seasar.mayaa.impl.source.ApplicationFileSourceDescriptor;
39+
import org.seasar.mayaa.impl.source.ClassLoaderSourceDescriptor;
40+
import org.seasar.mayaa.impl.source.HavingAliasSourceDescriptor;
41+
import org.seasar.mayaa.impl.util.StringUtil;
42+
import org.seasar.mayaa.source.SourceDescriptor;
43+
44+
/**
45+
* Webアプリケーションのコンテキスト内のリソースを走査してライブラリファイルを検出するための{@code SourceScanner}実装。
46+
* 走査対象は WEB-INF配下のリソースで、lib内のJARファイルも走査対象となる。
47+
*
48+
* <p>
49+
* ServiceProviderの設定ファイルで以下のように設定することで、走査対象のライブラリファイルを指定することができる。
50+
* 設定は記述順で評価され、最初に合致したものが採用される。
51+
* <p>
52+
* {@code include}と{@code exclude}は {@code /WEB-INF/}を基準としてライブラリファイル(tld,mld)を検出対象(include)または除外対象(exclude)するためのGlobパターンを指定する。記述順に評価され最初に合致したもので判定される。</br>
53+
* どちらのパターンも指定されない場合は{@code include="&#42;&#42;/&#42;.&#123;tld,mld&#125;"}のみが指定されたものとみなす。
54+
*
55+
* <p>
56+
* {@code includeJar}と{@code excludeJar}で走査対象JARファイルのWEB-INF/libを基準としたファイル名部分のGlobパターンを指定する。<br>
57+
* どちらのパターンも指定されない場合は {@code includeJar="*.jar"} のみが指定されたものとみなす。
58+
*
59+
* <p>
60+
* {@code includeInJarMetaInf}と{@code excludeInJarMetaInf}で読み込み対象とするライブラリファイルパスのJARファイル内のMETA-INFを基準としたGlobパターンを指定する。<br>
61+
* どちらのパターンも指定されない場合は {@code includeInJarMetaInf="&#42;.&#123;tld,mld&#125;"} のみが指定されたものとみなす。
62+
*
63+
* <p>
64+
* 記述例
65+
* <pre>
66+
* {@code
67+
*<scanner class="org.seasar.mayaa.impl.builder.library.scanner.WebInfSourceScanner">
68+
* <parameter name="exclude" value="&#123;classes,lib&#125;/"/>
69+
* <parameter name="include" value="&#42;&#42;/&#42;.&#123;tld,mld&#125;"/>
70+
* <parameter name="includeJar" value="taglibs-*.jar"/>
71+
* <parameter name="excludeInJarMetaInf" value="#123;x,sql,scriptfree,fn,permittedTaglibs#125;*"/>
72+
* <parameter name="includeInJarMetaInf" value="*.tld"/>
73+
*</scanner>
74+
* }
75+
* </pre>
76+
*
77+
* @param include 読み込み対象とするライブラリファイルパスのWEB-INFを基準としたGlobパターン
78+
* @param exclude 読み込み対象外とするライブラリファイルパスのWEB-INFを基準としたGlobパターン
79+
* @param includeJar 走査対象JARファイルのWEB-INF/libを基準としたファイル名部分のGlobパターン
80+
* @param excludeJar 走査対象外のJARファイルのWEB-INF/libを基準としたファイル名部分のGlobパターン
81+
* @param includeInJarMetaInf 読み込み対象とするライブラリファイルパスのJARファイル内のMETA-INFを基準としたGlobパターン
82+
* @param excludeInJarMetaInf 読み込み対象外とするライブラリファイルパスのJARファイル内のMETA-INFを基準としたGlobパターン
83+
*
84+
* @author Mitsutaka Watanabe <https://github.com/mitonize>
85+
*/
86+
public class WebInfSourceScanner extends NonSerializableParameterAwareImpl implements SourceScanner {
87+
private static final Log LOG = LogFactory.getLog(WebInfSourceScanner.class.getName());
88+
89+
private ApplicationFileSourceDescriptor _appSource;
90+
private Path _basePath;
91+
private ApplicationScope _appScope;
92+
private FileMatcher _fileMatchers;
93+
private FileMatcher _jarMatchers;
94+
private FileMatcher _inJarMetaInfMatchers;
95+
96+
public WebInfSourceScanner() {
97+
_appSource = new ApplicationFileSourceDescriptor();
98+
_appScope = _appSource.getApplicationScope();
99+
100+
_basePath = Paths.get(_appSource.getFile().toPath().toString(), "WEB-INF");
101+
_fileMatchers = new FileMatcher(_basePath);
102+
103+
_jarMatchers = new FileMatcher(Paths.get(_basePath.toString(), "lib"));
104+
_inJarMetaInfMatchers = new FileMatcher(Paths.get("META-INF"));
105+
}
106+
107+
/**
108+
* META-INF内の複数のJARファイルごとに生成される{@code SourceScanner}を集約することで
109+
* JARに含まれる{@code SourceDescriptor}を横断して走査するためのイテレータを返す。
110+
*
111+
* @see SourceScanner
112+
*/
113+
public Iterator<SourceDescriptor> scan() {
114+
// 条件が未設定の時はデフォルト値を設定する
115+
if (_fileMatchers.isEmpty()) {
116+
_fileMatchers.include("**/*.{tld,mld}");
117+
}
118+
if (_jarMatchers.isEmpty()) {
119+
_jarMatchers.include("*.jar");
120+
}
121+
if (_inJarMetaInfMatchers.isEmpty()) {
122+
_inJarMetaInfMatchers.include("*.{tld,mld}");
123+
}
124+
125+
List<SourceDescriptor> sources = new ArrayList<>();
126+
try {
127+
// ディレクトリを再起的に走査する
128+
Files.walkFileTree(_basePath, new FileVisitor<Path>() {
129+
@Override
130+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
131+
// PathMatcherでディレクトリ名に対するパターンが指定されていたらスキップする。
132+
if (_fileMatchers.matchesDirectory(dir)) {
133+
LOG.debug("TRAVERSE " + dir.toString());
134+
return FileVisitResult.CONTINUE;
135+
} else if (_jarMatchers.matchesDirectory(dir)) {
136+
LOG.debug("TRAVERSE " + dir.toString());
137+
return FileVisitResult.CONTINUE;
138+
}
139+
LOG.debug("SKIP " + dir.toString());
140+
return FileVisitResult.SKIP_SUBTREE;
141+
}
142+
143+
@Override
144+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
145+
if (_fileMatchers.matches(file)) {
146+
SourceDescriptor descriptor = toSourceDescriptor(_appScope, file);
147+
sources.add(descriptor);
148+
logSourceFound(descriptor);
149+
} else if (_jarMatchers.matches(file)) {
150+
sources.addAll(scanInJar(toSourceDescriptor(_appScope, file), _inJarMetaInfMatchers));
151+
}
152+
return FileVisitResult.CONTINUE;
153+
}
154+
155+
@Override
156+
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
157+
return FileVisitResult.CONTINUE;
158+
}
159+
160+
@Override
161+
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
162+
return FileVisitResult.CONTINUE;
163+
}
164+
});
165+
} catch (IOException e) {
166+
throw new RuntimeException(e);
167+
}
168+
169+
return sources.iterator();
170+
}
171+
172+
/**
173+
* 指定されたJARファイルを走査して指定されたMatcherに合致するライブラリファイルを検索する。
174+
*
175+
* @param sourceDescriptor JARファイルの{@code SourceDescriptor}を指定。
176+
* @param matchers JAR内で検索するライブラリファイルの条件を定義した{@code SourceDescriptor}を指定。
177+
* @return 合致するライブラリファイルの{@code SourceDescriptor}のリスト。
178+
* @throws RuntimeException JARファイルのスキャン中にIOエラーが発生した場合。
179+
*/
180+
List<SourceDescriptor> scanInJar(SourceDescriptor sourceDescriptor, FileMatcher matchers) {
181+
final String systemID = sourceDescriptor.getSystemID();
182+
final Path jarFilePath = Paths.get(systemID);
183+
184+
List<SourceDescriptor> sources = new ArrayList<>();
185+
try (JarInputStream jar = new JarInputStream(sourceDescriptor.getInputStream())) {
186+
JarEntry entry;
187+
while ((entry = jar.getNextJarEntry()) != null) {
188+
String entryName = entry.getName();
189+
if (entryName.startsWith("META-INF") && matchers.matches(Paths.get(entryName))) {
190+
191+
Date timestamp = sourceDescriptor.getTimestamp();
192+
193+
SourceAlias alias = new SourceAlias(jarFilePath.toString(), entryName, timestamp);
194+
ClassLoaderSourceDescriptor descriptor = new ClassLoaderSourceDescriptor();
195+
descriptor.setSystemID(alias.getSystemID());
196+
descriptor.setAlias(alias);
197+
descriptor.setTimestamp(alias.getTimestamp());
198+
sources.add(descriptor);
199+
logSourceFound(descriptor);
200+
}
201+
}
202+
return sources;
203+
} catch (IOException e) {
204+
throw new RuntimeException(e);
205+
}
206+
}
207+
208+
private void logSourceFound(SourceDescriptor descriptor) {
209+
if (LOG.isDebugEnabled()) {
210+
if (descriptor instanceof HavingAliasSourceDescriptor) {
211+
SourceAlias alias = ((HavingAliasSourceDescriptor) descriptor).getAlias();
212+
if (alias != null) {
213+
LOG.debug("FOUND " + alias.getAlias() + "!" + alias.getSystemID());
214+
return;
215+
}
216+
}
217+
LOG.debug("FOUND " + descriptor.getSystemID());
218+
}
219+
}
220+
221+
/**
222+
* 指定されたパスを{@code SourceDescriptor}に変換する。
223+
* @param appScope アプリケーションスコープ
224+
* @param path ファイルパス
225+
* @return 変換された{@code SourceDescriptor}
226+
*/
227+
private SourceDescriptor toSourceDescriptor(ApplicationScope appScope, Path path) {
228+
Path p = _basePath.relativize(path);
229+
final String systemID = StringUtil.preparePath("/WEB-INF/" + p.toString());
230+
231+
ApplicationFileSourceDescriptor source = new ApplicationFileSourceDescriptor();
232+
source.setRoot(null);
233+
source.setSystemID(systemID);
234+
source.setFile(path.toFile());
235+
236+
return source;
237+
}
238+
239+
// Parameterizable implements ------------------------------------
240+
public void setParameter(String name, String value) {
241+
LOG.info("CONFIG " + name + "=" + value);
242+
if ("include".equals(name)) {
243+
_fileMatchers.include(value);
244+
} else if ("includeJar".equals(name)) {
245+
_jarMatchers.include(value);
246+
} else if ("includeInJarMetaInf".equals(name)) {
247+
_inJarMetaInfMatchers.include(value);
248+
} else if ("exclude".equals(name)) {
249+
_fileMatchers.exclude(value);
250+
} else if ("excludeJar".equals(name)) {
251+
_jarMatchers.exclude(value);
252+
} else if ("excludeInJarMetaInf".equals(name)) {
253+
_inJarMetaInfMatchers.exclude(value);
254+
}
255+
super.setParameter(name, value);
256+
}
257+
258+
259+
/**
260+
* ファイル名に対してGlobパターンで含む(include)・除外する(exclude)を順番に追加管理する。
261+
* 評価時は指定された順番で行い、最初にパターンに一致した結果を返す.
262+
* いずれにもマッチしない場合はデフォルト値を返す。デフォルト値の初期値は {@code false} (マッチしない)である。
263+
*/
264+
final class FileMatcher implements PathMatcher {
265+
private List<FileMatcherElement> _fileMatchers = new ArrayList<>();
266+
private List<FileMatcherElement> _directoryMatchers = new ArrayList<>();
267+
private boolean _defaultResult = true;
268+
private Path _basePath;
269+
270+
FileMatcher(Path basePath) {
271+
this(basePath, false);
272+
}
273+
274+
FileMatcher(Path basePath, boolean defaultResult) {
275+
_basePath = basePath;
276+
_defaultResult = defaultResult;
277+
}
278+
279+
class FileMatcherElement {
280+
boolean isInclude;
281+
PathMatcher matcher;
282+
FileMatcherElement(String globPattern, boolean isInclude) {
283+
this.isInclude = isInclude;
284+
this.matcher = FileSystems.getDefault().getPathMatcher("glob:" + globPattern);
285+
}
286+
}
287+
288+
void include(String globPattern) {
289+
_fileMatchers.add(new FileMatcherElement(globPattern, true));
290+
int lastSlashIndex = globPattern.lastIndexOf('/');
291+
if (lastSlashIndex > 0) {
292+
_directoryMatchers.add(new FileMatcherElement(globPattern.substring(0, lastSlashIndex), true));
293+
}
294+
}
295+
void exclude(String globPattern) {
296+
_fileMatchers.add(new FileMatcherElement(globPattern, false));
297+
int lastSlashIndex = globPattern.lastIndexOf('/');
298+
if (lastSlashIndex > 0) {
299+
_directoryMatchers.add(new FileMatcherElement(globPattern.substring(0, lastSlashIndex), false));
300+
}
301+
}
302+
303+
@Override
304+
public boolean matches(Path path) {
305+
path = _basePath.relativize(path);
306+
for (FileMatcherElement elm: _fileMatchers) {
307+
final boolean match = elm.matcher.matches(path);
308+
if (match) {
309+
// 最初にマッチした結果を返す。
310+
return elm.isInclude;
311+
}
312+
}
313+
return _defaultResult;
314+
}
315+
316+
public boolean matchesDirectory(Path path) {
317+
path = _basePath.relativize(path);
318+
if (path.toString().isEmpty()) {
319+
return true;
320+
}
321+
for (FileMatcherElement elm: _directoryMatchers) {
322+
final boolean match = elm.matcher.matches(path);
323+
if (match) {
324+
// 最初にマッチした結果を返す。
325+
return elm.isInclude;
326+
}
327+
}
328+
return _defaultResult;
329+
}
330+
331+
boolean isEmpty() {
332+
return _fileMatchers.isEmpty();
333+
}
334+
}
335+
336+
}

src-impl/org/seasar/mayaa/impl/provider/factory/org.seasar.mayaa.provider.ServiceProvider

+8-21
Original file line numberDiff line numberDiff line change
@@ -54,27 +54,14 @@
5454
<libraryManager class="org.seasar.mayaa.impl.builder.library.LibraryManagerImpl">
5555
<converter name="ProcessorProperty" class="org.seasar.mayaa.impl.builder.library.converter.ProcessorPropertyConverter"/>
5656
<converter name="PrefixAwareName" class="org.seasar.mayaa.impl.builder.library.converter.PrefixAwareNameConverter"/>
57-
<scanner class="org.seasar.mayaa.impl.builder.library.scanner.FolderSourceScanner">
58-
<parameter name="folder" value="/WEB-INF"/>
59-
<parameter name="recursive" value="true"/>
60-
<parameter name="extension" value=".tld"/>
61-
<parameter name="extension" value=".mld"/>
62-
</scanner>
63-
<scanner class="org.seasar.mayaa.impl.builder.library.scanner.MetaInfSourceScanner">
64-
<parameter name="folder" value="/WEB-INF/lib"/>
65-
<parameter name="extension" value=".jar"/>
66-
<parameter name="ignore" value="commons-beanutils-"/>
67-
<parameter name="ignore" value="commons-collections-"/>
68-
<parameter name="ignore" value="commons-logging-"/>
69-
<parameter name="ignore" value="nekohtml-"/>
70-
<parameter name="ignore" value="jaxen-"/>
71-
<parameter name="ignore" value="xml-apis-"/>
72-
<parameter name="ignore" value="xercesImpl-"/>
73-
<parameter name="ignore" value="rhino-"/>
74-
<parameter name="jar.ignore" value="META-INF/MANIFEST.MF"/>
75-
<parameter name="jar.extension" value=".mld"/>
76-
<parameter name="jar.extension" value=".tld"/>
57+
58+
<scanner class="org.seasar.mayaa.impl.builder.library.scanner.WebInfSourceScanner">
59+
<!-- <parameter name="include" value="**/*.{tld,mld}"/> -->
60+
<parameter name="excludeJar" value="commons-beanutils,commons-collections,commons-logging,nekohtml,jaxen,xml-apis,xercesImpl,rhino}-*.jar"/>
61+
<parameter name="includeJar" value="*.jar"/>
62+
<!-- <parameter name="includeInJarMetaInf" value="*.tld"/> -->
7763
</scanner>
64+
7865
<scanner class="org.seasar.mayaa.impl.builder.library.scanner.ResourceScanner">
7966
<parameter name="root" value="META-INF/"/>
8067
<parameter name="ignore" value="META-INF/MANIFEST.MF"/>
@@ -85,7 +72,7 @@
8572
<scanner class="org.seasar.mayaa.impl.builder.library.scanner.DefaultSourceScanner"/>
8673

8774
<!-- after scan jars -->
88-
<scanner class="org.seasar.mayaa.impl.builder.library.scanner.WebXMLTaglibSourceScanner"/>
75+
<!-- <scanner class="org.seasar.mayaa.impl.builder.library.scanner.WebXMLTaglibSourceScanner"/> -->
8976

9077
<builder class="org.seasar.mayaa.impl.builder.library.MLDDefinitionBuilder"/>
9178
<builder class="org.seasar.mayaa.impl.builder.library.TLDDefinitionBuilder"/>

0 commit comments

Comments
 (0)