JBossEAP6とJerseyの組合せでjar内のJAX-RSリソースが見えない件
そもそもなんでJBossにJerseyなんだ?なんて聞かないで。大人の事情なんです。
問題
warファイルをJBossEAPにデプロイしたんだが、WEB-INF/lib配下のjarファイルに入ってるエンティティプロバイダクラス(@Providerついてるクラス)が有効になんねぇ。。。
Tomcatだと動くのに(´・ω・`)
環境
- JDK1.7u45
- JBossEAP6.1.1
- Jersey1.17.1(Spring連携)
- warファイルでデプロイ
設定
■web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>RESTApplication-sample</display-name> <!-- Springのbean定義ファイル --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- log4jの設定ファイル --> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/classes/log4j.xml</param-value> </context-param> <context-param> <param-name>webAppRootKey</param-name> <param-value>REST Application</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <!-- Spring連携用のJerseyサーブレット(Spring連携用じゃなくてもいい) --> <servlet> <servlet-name>Jersey Spring Web Application</servlet-name> <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class> <!-- ↓①↓ --> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>org.codehaus.jackson.jaxrs</param-value> </init-param> <!-- ↑①↑ --> <init-param> <param-name>com.sun.jersey.config.property.WebPageContentRegex</param-name> <param-value>/.*[\\.]html</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>Jersey Spring Web Application</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
①の設定で「org.codehaus.jackson.jaxrs」パッケージ以下のエンティティプロバイダ(@Providerついてるやつ)とかリソースクラスをロードしてくれるはずなんだけど。。。
■jboss-deployment-structure.xml
<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2"> <deployment> <exclude-subsystems> <subsystem name="jaxrs" /> </exclude-subsystems> </deployment> </jboss-deployment-structure>
RESTEasy無効化の設定。
■standalone.xml
以下の行をコメントアウト
<extension module="org.jboss.as.jaxrs"/> <subsystem xmlns="urn:jboss:domain:jaxrs:1.0"/>
RESTEasy無効化の設定。
原因
com.sun.jersey.spi.container.servlet.ServletContainer(com.sun.jersey.spi.spring.container.servlet.SpringServletの親)のinit()メソッドのちょっと奥で生成されているcom.sun.jersey.core.spi.scanning.PackageNamesScannerのコンストラクタが下記。
public PackageNamesScanner(final ClassLoader classloader, final String[] packages) { this.packages = packages; this.classloader = classloader; this.scanners = new HashMap<String, UriSchemeScanner>(); add(new JarZipSchemeScanner()); add(new FileSchemeScanner()); add(new VfsSchemeScanner()); // ② add(new BundleSchemeScanner()); for (UriSchemeScanner s : ServiceFinder.find(UriSchemeScanner.class)) { // ③ add(s); } } private void add(final UriSchemeScanner ss) { for (final String s : ss.getSchemes()) { scanners.put(s.toLowerCase(), ss); } } // 〜省略〜 private void scan(final URI u, final ScannerListener cfl) { final UriSchemeScanner ss = scanners.get(u.getScheme().toLowerCase()); if (ss != null) { ss.scan(u, cfl); } else { throw new ScannerException("The URI scheme " + u.getScheme() + " of the URI " + u + " is not supported. Package scanning deployment is not" + " supported for such URIs." + "\nTry using a different deployment mechanism such as" + " explicitly declaring root resource and provider classes" + " using an extension of javax.ws.rs.core.Application"); } }
scannersというMapにKeyがスキーム、ValueがUriSchemeScannerの継承クラスでaddしてる。
このscannersがJerseyサーブレットで必要なリソース(エンティティプロバイダとか)を文字通りスキャンする。
で、②のVfsSchemeScannerってのがJavadocによるとJBoss用のスキャナなんだが、こいつが以下の通り。
package com.sun.jersey.core.spi.scanning.uri; // 〜省略〜 public class VfsSchemeScanner implements UriSchemeScanner { public Set<String> getSchemes() { return new HashSet<String>(Arrays.asList("vfsfile", "vfszip", "vfs")); } // UriSchemeScanner public void scan(final URI u, final ScannerListener sl) { if (!u.getScheme().equalsIgnoreCase("vfszip")) { new FileSchemeScanner().scan( UriBuilder.fromUri(u).scheme("file").build(), sl); } else { final String su = u.toString(); // ④ final int webInfIndex = su.indexOf("/WEB-INF/classes"); if (webInfIndex != -1) { final String war = su.substring(0, webInfIndex); final String path = su.substring(webInfIndex + 1); final int warParentIndex = war.lastIndexOf('/'); final String warParent = su.substring(0, warParentIndex); // Check is there is a war within an ear // If so we need to load the ear then obtain the InputStream // of the entry to the war if (warParent.endsWith(".ear")) { final String warName = su.substring(warParentIndex + 1, war.length()); try { JarFileScanner.scan(new URL(warParent.replace("vfszip", "file")).openStream(), "", new ScannerListener() { public boolean onAccept(String name) { return name.equals(warName); } public void onProcess(String name, InputStream in) throws IOException { // This is required so that the underlying ear // is not closed in = new FilterInputStream(in) { public void close() throws IOException {}; }; try { JarFileScanner.scan(in, path, sl); } catch (IOException ex) { throw new ScannerException("IO error when scanning war " + u, ex); } } }); } catch (IOException ex) { throw new ScannerException("IO error when scanning war " + u, ex); } } else { try { JarFileScanner.scan(new URL(war.replace("vfszip", "file")).openStream(), path, sl); } catch (IOException ex) { throw new ScannerException("IO error when scanning war " + u, ex); } } } else { try { JarFileScanner.scan(new URL(su).openStream(), "", sl); } catch (IOException ex) { throw new ScannerException("IO error when scanning jar " + u, ex); } } } } }
④の変数suには「vfs:/[デプロイ先のパス]/SampleApp.war/WEB-INF/lib/jackson-jaxrs-1.9.2.jar/org/codehaus/jackson/jaxrs/」みたいなのが入ってる。
これ見るとscanメソッドがサーブレットinitで後々実行されるんだけど、「/WEB-INF/classes」配下しか見てなくね?(´・ω・`)
「/WEB-INF/lib」配下も見てほしい。。。てか「vfsfile」「vfszip」「vfs」スキームってなんやねん!
解法
幸いなことに③でscannersマップを上書きできる余地を残してくれてる!
classes配下に「META-INF/services」ディレクトリを作って、その中に「com.sun.jersey.core.spi.scanning.uri.UriSchemeScanner」ファイルを作って
UriSchemeScannerを継承して作ったクラスのフルネーム(「org.sample.scanning.uri.VfsSchemeScannerFix」とか)
を書いておけば②の「VfsSchemeScanner」を別のクラスに差し替え可能だ!
③の詳細は「ServiceLoader」を調べてね。
参考:http://itpro.nikkeibp.co.jp/article/COLUMN/20061215/257003/
で、VfsSchemeScannerの代わりを下記に作成。
■org.sample.scanning.uri.VfsSchemeScannerFix
package org.sample.scanning.uri; import java.io.IOException; import java.net.URI; import java.net.URL; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import com.sun.jersey.core.spi.scanning.JarFileScanner; import com.sun.jersey.core.spi.scanning.ScannerException; import com.sun.jersey.core.spi.scanning.ScannerListener; import com.sun.jersey.core.spi.scanning.uri.UriSchemeScanner; public class VfsSchemeScannerFix implements UriSchemeScanner { @Override public Set<String> getSchemes() { return new HashSet<String>(Arrays.asList("vfs")); } @Override public void scan(final URI u, final ScannerListener sl) throws ScannerException { if (u.getScheme().equalsIgnoreCase("vfs")) { String su = u.toString(); int jarIdx = su.lastIndexOf(".jar"); String jar = su.substring(0, jarIdx + 4); String path = su.substring(jarIdx + 5); try { JarFileScanner.scan( new URL(jar.replace("vfs", "file")).openStream(), path, sl); } catch (IOException ex) { throw new ScannerException("IO error when scanning jar " + u, ex); } } } }
一応動いてる。
参考
SAStrutsをJBossAS7で動かそう(体験談)
http://tech-sketch.jp/2012/09/sastrutsjbossas7.html
[#JERSEY-763] VfsSchemeScanner cannot be run in Jboss-7 environment - Java.net JIRA
https://java.net/jira/browse/JERSEY-763
Quick Fix Jersey VfsSchemeScanner can not Recognize vfs:/ Scheme Issue
http://my.opera.com/Vancoole/blog/quick-fix-jersey-vfsschemescanner-can-not-recognize-vfs-scheme-issue