-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from harawata/custom-vfs
Demonstrating a fix using custom VFS implementation.
- Loading branch information
Showing
3 changed files
with
363 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
361 changes: 361 additions & 0 deletions
361
mybatis-typehandler/src/main/java/sample/infra/mybatis/vfs/SpringBootExecutableJarVFS.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,361 @@ | ||
/** | ||
* Copyright 2009-2015 the original author or authors. | ||
* | ||
* 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 sample.infra.mybatis.vfs; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.File; | ||
import java.io.FileNotFoundException; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.InputStreamReader; | ||
import java.io.UnsupportedEncodingException; | ||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import java.net.URLEncoder; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.jar.JarEntry; | ||
import java.util.jar.JarInputStream; | ||
|
||
import org.apache.ibatis.io.VFS; | ||
import org.apache.ibatis.logging.Log; | ||
import org.apache.ibatis.logging.LogFactory; | ||
|
||
/** | ||
* Modified DefaultVFS for handling nested jar. | ||
*/ | ||
public class SpringBootExecutableJarVFS extends VFS { | ||
private static final Log log = LogFactory.getLog(SpringBootExecutableJarVFS.class); | ||
|
||
/** The magic header that indicates a JAR (ZIP) file. */ | ||
private static final byte[] JAR_MAGIC = { 'P', 'K', 3, 4 }; | ||
|
||
@Override | ||
public boolean isValid() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public List<String> list(URL url, String path) throws IOException { | ||
InputStream is = null; | ||
try { | ||
List<String> resources = new ArrayList<String>(); | ||
|
||
// First, try to find the URL of a JAR file containing the requested resource. If a JAR | ||
// file is found, then we'll list child resources by reading the JAR. | ||
URL jarUrl = findJarForResource(url); | ||
if (jarUrl != null) { | ||
is = jarUrl.openStream(); | ||
if (log.isDebugEnabled()) { | ||
log.debug("Listing " + url); | ||
} | ||
resources = listResources(new JarInputStream(is), path); | ||
} | ||
else { | ||
List<String> children = new ArrayList<String>(); | ||
try { | ||
if (isJar(url)) { | ||
// Some versions of JBoss VFS might give a JAR stream even if the resource | ||
// referenced by the URL isn't actually a JAR | ||
is = url.openStream(); | ||
JarInputStream jarInput = new JarInputStream(is); | ||
if (log.isDebugEnabled()) { | ||
log.debug("Listing " + url); | ||
} | ||
for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Jar entry: " + entry.getName()); | ||
} | ||
children.add(entry.getName()); | ||
} | ||
jarInput.close(); | ||
} | ||
else { | ||
/* | ||
* Some servlet containers allow reading from directory resources like a | ||
* text file, listing the child resources one per line. However, there is no | ||
* way to differentiate between directory and file resources just by reading | ||
* them. To work around that, as each line is read, try to look it up via | ||
* the class loader as a child of the current resource. If any line fails | ||
* then we assume the current resource is not a directory. | ||
*/ | ||
is = url.openStream(); | ||
BufferedReader reader = new BufferedReader(new InputStreamReader(is)); | ||
List<String> lines = new ArrayList<String>(); | ||
for (String line; (line = reader.readLine()) != null;) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Reader entry: " + line); | ||
} | ||
lines.add(line); | ||
if (getResources(path + "/" + line).isEmpty()) { | ||
lines.clear(); | ||
break; | ||
} | ||
} | ||
|
||
if (!lines.isEmpty()) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Listing " + url); | ||
} | ||
children.addAll(lines); | ||
} | ||
} | ||
} catch (FileNotFoundException e) { | ||
/* | ||
* For file URLs the openStream() call might fail, depending on the servlet | ||
* container, because directories can't be opened for reading. If that happens, | ||
* then list the directory directly instead. | ||
*/ | ||
if ("file".equals(url.getProtocol())) { | ||
File file = new File(url.getFile()); | ||
if (log.isDebugEnabled()) { | ||
log.debug("Listing directory " + file.getAbsolutePath()); | ||
} | ||
if (file.isDirectory()) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Listing " + url); | ||
} | ||
children = Arrays.asList(file.list()); | ||
} | ||
} | ||
else { | ||
// No idea where the exception came from so rethrow it | ||
throw e; | ||
} | ||
} | ||
|
||
// The URL prefix to use when recursively listing child resources | ||
String prefix = url.toExternalForm(); | ||
if (!prefix.endsWith("/")) { | ||
prefix = prefix + "/"; | ||
} | ||
|
||
// Iterate over immediate children, adding files and recursing into directories | ||
for (String child : children) { | ||
String resourcePath = path + "/" + child; | ||
resources.add(resourcePath); | ||
URL childUrl = new URL(prefix + child); | ||
resources.addAll(list(childUrl, resourcePath)); | ||
} | ||
} | ||
|
||
return resources; | ||
} finally { | ||
if (is != null) { | ||
try { | ||
is.close(); | ||
} catch (Exception e) { | ||
// Ignore | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* List the names of the entries in the given {@link JarInputStream} that begin with the | ||
* specified {@code path}. Entries will match with or without a leading slash. | ||
* | ||
* @param jar The JAR input stream | ||
* @param path The leading path to match | ||
* @return The names of all the matching entries | ||
* @throws IOException If I/O errors occur | ||
*/ | ||
protected List<String> listResources(JarInputStream jar, String path) throws IOException { | ||
// Include the leading and trailing slash when matching names | ||
if (!path.startsWith("/")) { | ||
path = "/" + path; | ||
} | ||
if (!path.endsWith("/")) { | ||
path = path + "/"; | ||
} | ||
|
||
// Iterate over the entries and collect those that begin with the requested path | ||
List<String> resources = new ArrayList<String>(); | ||
for (JarEntry entry; (entry = jar.getNextJarEntry()) != null;) { | ||
if (!entry.isDirectory()) { | ||
// Add leading slash if it's missing | ||
String name = entry.getName(); | ||
if (!name.startsWith("/")) { | ||
name = "/" + name; | ||
} | ||
|
||
// Check file name | ||
if (name.startsWith(path)) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Found resource: " + name); | ||
} | ||
// Trim leading slash | ||
resources.add(name.substring(1)); | ||
} | ||
} | ||
} | ||
return resources; | ||
} | ||
|
||
/** | ||
* Attempts to deconstruct the given URL to find a JAR file containing the resource referenced | ||
* by the URL. That is, assuming the URL references a JAR entry, this method will return a URL | ||
* that references the JAR file containing the entry. If the JAR cannot be located, then this | ||
* method returns null. | ||
* | ||
* @param url The URL of the JAR entry. | ||
* @return The URL of the JAR file, if one is found. Null if not. | ||
* @throws MalformedURLException | ||
*/ | ||
protected URL findJarForResource(URL url) throws MalformedURLException { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Find JAR URL: " + url); | ||
} | ||
|
||
if (isNestedJar(url)) { | ||
// Retain jar: protocol as a workaround for #325 | ||
if (log.isDebugEnabled()) { | ||
log.debug("It is a nested JAR: " + url); | ||
} | ||
} else { | ||
// If the file part of the URL is itself a URL, then that URL probably points to the JAR | ||
try { | ||
for (;;) { | ||
url = new URL(url.getFile()); | ||
if (log.isDebugEnabled()) { | ||
log.debug("Inner URL: " + url); | ||
} | ||
} | ||
} catch (MalformedURLException e) { | ||
// This will happen at some point and serves as a break in the loop | ||
} | ||
} | ||
|
||
// Look for the .jar extension and chop off everything after that | ||
StringBuilder jarUrl = new StringBuilder(url.toExternalForm()); | ||
int index = jarUrl.lastIndexOf(".jar"); | ||
if (index >= 0) { | ||
jarUrl.setLength(index + 4); | ||
if (log.isDebugEnabled()) { | ||
log.debug("Extracted JAR URL: " + jarUrl); | ||
} | ||
} | ||
else { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Not a JAR: " + jarUrl); | ||
} | ||
return null; | ||
} | ||
|
||
// Try to open and test it | ||
try { | ||
URL testUrl = new URL(jarUrl.toString()); | ||
if (isJar(testUrl)) { | ||
return testUrl; | ||
} | ||
else { | ||
// WebLogic fix: check if the URL's file exists in the filesystem. | ||
if (log.isDebugEnabled()) { | ||
log.debug("Not a JAR: " + jarUrl); | ||
} | ||
jarUrl.replace(0, jarUrl.length(), testUrl.getFile()); | ||
File file = new File(jarUrl.toString()); | ||
|
||
// File name might be URL-encoded | ||
if (!file.exists()) { | ||
try { | ||
file = new File(URLEncoder.encode(jarUrl.toString(), "UTF-8")); | ||
} catch (UnsupportedEncodingException e) { | ||
throw new RuntimeException("Unsupported encoding? UTF-8? That's unpossible."); | ||
} | ||
} | ||
|
||
if (file.exists()) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Trying real file: " + file.getAbsolutePath()); | ||
} | ||
testUrl = file.toURI().toURL(); | ||
if (isJar(testUrl)) { | ||
return testUrl; | ||
} | ||
} | ||
} | ||
} catch (MalformedURLException e) { | ||
log.warn("Invalid JAR URL: " + jarUrl); | ||
} | ||
|
||
if (log.isDebugEnabled()) { | ||
log.debug("Not a JAR: " + jarUrl); | ||
} | ||
return null; | ||
} | ||
|
||
protected boolean isNestedJar(URL url) { | ||
if (!"jar".equals(url.getProtocol())) | ||
return false; | ||
String urlStr = url.toExternalForm(); | ||
return urlStr.indexOf(".jar!/") != urlStr.lastIndexOf(".jar!/"); | ||
} | ||
|
||
/** | ||
* Converts a Java package name to a path that can be looked up with a call to | ||
* {@link ClassLoader#getResources(String)}. | ||
* | ||
* @param packageName The Java package name to convert to a path | ||
*/ | ||
protected String getPackagePath(String packageName) { | ||
return packageName == null ? null : packageName.replace('.', '/'); | ||
} | ||
|
||
/** | ||
* Returns true if the resource located at the given URL is a JAR file. | ||
* | ||
* @param url The URL of the resource to test. | ||
*/ | ||
protected boolean isJar(URL url) { | ||
return isJar(url, new byte[JAR_MAGIC.length]); | ||
} | ||
|
||
/** | ||
* Returns true if the resource located at the given URL is a JAR file. | ||
* | ||
* @param url The URL of the resource to test. | ||
* @param buffer A buffer into which the first few bytes of the resource are read. The buffer | ||
* must be at least the size of {@link #JAR_MAGIC}. (The same buffer may be reused | ||
* for multiple calls as an optimization.) | ||
*/ | ||
protected boolean isJar(URL url, byte[] buffer) { | ||
InputStream is = null; | ||
try { | ||
is = url.openStream(); | ||
is.read(buffer, 0, JAR_MAGIC.length); | ||
if (Arrays.equals(buffer, JAR_MAGIC)) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Found JAR: " + url); | ||
} | ||
return true; | ||
} | ||
} catch (Exception e) { | ||
// Failure to read the stream means this is not a JAR | ||
} finally { | ||
if (is != null) { | ||
try { | ||
is.close(); | ||
} catch (Exception e) { | ||
// Ignore | ||
} | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters