• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / mybatis-3 / 2604

03 Jan 2025 10:00AM UTC coverage: 87.524% (+0.3%) from 87.177%
2604

Pull #3146

github

web-flow
Merge 60c1f5fea into 8ac3920af
Pull Request #3146: Shared ambiguity instance

3633 of 4401 branches covered (82.55%)

4 of 4 new or added lines in 1 file covered. (100.0%)

254 existing lines in 22 files now uncovered.

9569 of 10933 relevant lines covered (87.52%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

39.71
/src/main/java/org/apache/ibatis/io/DefaultVFS.java
1
/*
2
 *    Copyright 2009-2024 the original author or authors.
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
 *       https://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, either express or implied.
13
 *    See the License for the specific language governing permissions and
14
 *    limitations under the License.
15
 */
16
package org.apache.ibatis.io;
17

18
import java.io.BufferedReader;
19
import java.io.File;
20
import java.io.FileNotFoundException;
21
import java.io.IOException;
22
import java.io.InputStream;
23
import java.io.InputStreamReader;
24
import java.net.MalformedURLException;
25
import java.net.URL;
26
import java.net.URLEncoder;
27
import java.nio.charset.StandardCharsets;
28
import java.nio.file.FileSystemException;
29
import java.nio.file.InvalidPathException;
30
import java.util.ArrayList;
31
import java.util.Arrays;
32
import java.util.List;
33
import java.util.jar.JarEntry;
34
import java.util.jar.JarInputStream;
35

36
import org.apache.ibatis.logging.Log;
37
import org.apache.ibatis.logging.LogFactory;
38

39
/**
40
 * A default implementation of {@link VFS} that works for most application servers.
41
 *
42
 * @author Ben Gunter
43
 */
44
public class DefaultVFS extends VFS {
1✔
45
  private static final Log log = LogFactory.getLog(DefaultVFS.class);
1✔
46

47
  /** The magic header that indicates a JAR (ZIP) file. */
48
  private static final byte[] JAR_MAGIC = { 'P', 'K', 3, 4 };
1✔
49

50
  @Override
51
  public boolean isValid() {
52
    return true;
1✔
53
  }
54

55
  @Override
56
  public List<String> list(URL url, String path) throws IOException {
57
    InputStream is = null;
1✔
58
    try {
59
      List<String> resources = new ArrayList<>();
1✔
60

61
      // First, try to find the URL of a JAR file containing the requested resource. If a JAR
62
      // file is found, then we'll list child resources by reading the JAR.
63
      URL jarUrl = findJarForResource(url);
1✔
64
      if (jarUrl != null) {
1!
UNCOV
65
        is = jarUrl.openStream();
×
66
        if (log.isDebugEnabled()) {
×
67
          log.debug("Listing " + url);
×
68
        }
UNCOV
69
        resources = listResources(new JarInputStream(is), path);
×
70
      } else {
71
        List<String> children = new ArrayList<>();
1✔
72
        try {
73
          if (isJar(url)) {
1!
74
            // Some versions of JBoss VFS might give a JAR stream even if the resource
75
            // referenced by the URL isn't actually a JAR
UNCOV
76
            is = url.openStream();
×
77
            try (JarInputStream jarInput = new JarInputStream(is)) {
×
78
              if (log.isDebugEnabled()) {
×
79
                log.debug("Listing " + url);
×
80
              }
UNCOV
81
              File destinationDir = new File(path);
×
82
              for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;) {
×
83
                if (log.isDebugEnabled()) {
×
84
                  log.debug("Jar entry: " + entry.getName());
×
85
                }
86
                File entryFile = new File(destinationDir, entry.getName()).getCanonicalFile();
×
UNCOV
87
                if (!entryFile.getPath().startsWith(destinationDir.getCanonicalPath())) {
×
UNCOV
88
                  throw new IOException("Bad zip entry: " + entry.getName());
×
89
                }
UNCOV
90
                children.add(entry.getName());
×
UNCOV
91
              }
×
92
            }
93
          } else {
94
            /*
95
             * Some servlet containers allow reading from directory resources like a text file, listing the child
96
             * resources one per line. However, there is no way to differentiate between directory and file resources
97
             * just by reading them. To work around that, as each line is read, try to look it up via the class loader
98
             * as a child of the current resource. If any line fails then we assume the current resource is not a
99
             * directory.
100
             */
101
            is = url.openStream();
1✔
102
            List<String> lines = new ArrayList<>();
1✔
103
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
1✔
104
              for (String line; (line = reader.readLine()) != null;) {
1✔
105
                if (log.isDebugEnabled()) {
1!
UNCOV
106
                  log.debug("Reader entry: " + line);
×
107
                }
108
                lines.add(line);
1✔
109
                if (getResources(path + "/" + line).isEmpty()) {
1✔
110
                  lines.clear();
1✔
111
                  break;
1✔
112
                }
113
              }
UNCOV
114
            } catch (InvalidPathException | FileSystemException e) {
×
115
              // #1974 #2598
116
              lines.clear();
×
117
            }
1✔
118
            if (!lines.isEmpty()) {
1✔
119
              if (log.isDebugEnabled()) {
1!
UNCOV
120
                log.debug("Listing " + url);
×
121
              }
122
              children.addAll(lines);
1✔
123
            }
124
          }
UNCOV
125
        } catch (FileNotFoundException e) {
×
126
          /*
127
           * For file URLs the openStream() call might fail, depending on the servlet container, because directories
128
           * can't be opened for reading. If that happens, then list the directory directly instead.
129
           */
130
          if (!"file".equals(url.getProtocol())) {
×
131
            // No idea where the exception came from so rethrow it
132
            throw e;
×
133
          }
134
          File file = new File(url.getFile());
×
135
          if (log.isDebugEnabled()) {
×
136
            log.debug("Listing directory " + file.getAbsolutePath());
×
137
          }
138
          if (file.isDirectory()) {
×
UNCOV
139
            if (log.isDebugEnabled()) {
×
UNCOV
140
              log.debug("Listing " + url);
×
141
            }
UNCOV
142
            children = Arrays.asList(file.list());
×
143
          }
144
        }
1✔
145

146
        // The URL prefix to use when recursively listing child resources
147
        String prefix = url.toExternalForm();
1✔
148
        if (!prefix.endsWith("/")) {
1!
149
          prefix = prefix + "/";
1✔
150
        }
151

152
        // Iterate over immediate children, adding files and recurring into directories
153
        for (String child : children) {
1✔
154
          String resourcePath = path + "/" + child;
1✔
155
          resources.add(resourcePath);
1✔
156
          URL childUrl = new URL(prefix + child);
1✔
157
          resources.addAll(list(childUrl, resourcePath));
1✔
158
        }
1✔
159
      }
160

161
      return resources;
1✔
162
    } finally {
163
      if (is != null) {
1!
164
        try {
165
          is.close();
1✔
UNCOV
166
        } catch (Exception e) {
×
167
          // Ignore
168
        }
1✔
169
      }
170
    }
171
  }
172

173
  /**
174
   * List the names of the entries in the given {@link JarInputStream} that begin with the specified {@code path}.
175
   * Entries will match with or without a leading slash.
176
   *
177
   * @param jar
178
   *          The JAR input stream
179
   * @param path
180
   *          The leading path to match
181
   *
182
   * @return The names of all the matching entries
183
   *
184
   * @throws IOException
185
   *           If I/O errors occur
186
   */
187
  protected List<String> listResources(JarInputStream jar, String path) throws IOException {
188
    // Include the leading and trailing slash when matching names
189
    if (!path.startsWith("/")) {
×
UNCOV
190
      path = "/" + path;
×
191
    }
UNCOV
192
    if (!path.endsWith("/")) {
×
193
      path = path + "/";
×
194
    }
195

196
    // Iterate over the entries and collect those that begin with the requested path
197
    List<String> resources = new ArrayList<>();
×
198
    for (JarEntry entry; (entry = jar.getNextJarEntry()) != null;) {
×
199
      if (!entry.isDirectory()) {
×
200
        // Add leading slash if it's missing
UNCOV
201
        StringBuilder name = new StringBuilder(entry.getName());
×
UNCOV
202
        if (name.charAt(0) != '/') {
×
203
          name.insert(0, '/');
×
204
        }
205

206
        // Check file name
UNCOV
207
        if (name.indexOf(path) == 0) {
×
208
          if (log.isDebugEnabled()) {
×
UNCOV
209
            log.debug("Found resource: " + name);
×
210
          }
211
          // Trim leading slash
212
          resources.add(name.substring(1));
×
213
        }
UNCOV
214
      }
×
215
    }
UNCOV
216
    return resources;
×
217
  }
218

219
  /**
220
   * Attempts to deconstruct the given URL to find a JAR file containing the resource referenced by the URL. That is,
221
   * assuming the URL references a JAR entry, this method will return a URL that references the JAR file containing the
222
   * entry. If the JAR cannot be located, then this method returns null.
223
   *
224
   * @param url
225
   *          The URL of the JAR entry.
226
   *
227
   * @return The URL of the JAR file, if one is found. Null if not.
228
   *
229
   * @throws MalformedURLException
230
   *           the malformed URL exception
231
   */
232
  protected URL findJarForResource(URL url) throws MalformedURLException {
233
    if (log.isDebugEnabled()) {
1!
UNCOV
234
      log.debug("Find JAR URL: " + url);
×
235
    }
236

237
    // If the file part of the URL is itself a URL, then that URL probably points to the JAR
238
    boolean continueLoop = true;
1✔
239
    while (continueLoop) {
1✔
240
      try {
UNCOV
241
        url = new URL(url.getFile());
×
UNCOV
242
        if (log.isDebugEnabled()) {
×
UNCOV
243
          log.debug("Inner URL: " + url);
×
244
        }
245
      } catch (MalformedURLException e) {
1✔
246
        // This will happen at some point and serves as a break in the loop
247
        continueLoop = false;
1✔
248
      }
1✔
249
    }
250

251
    // Look for the .jar extension and chop off everything after that
252
    StringBuilder jarUrl = new StringBuilder(url.toExternalForm());
1✔
253
    int index = jarUrl.lastIndexOf(".jar");
1✔
254
    if (index < 0) {
1!
255
      if (log.isDebugEnabled()) {
1!
256
        log.debug("Not a JAR: " + jarUrl);
×
257
      }
258
      return null;
1✔
259
    }
UNCOV
260
    jarUrl.setLength(index + 4);
×
UNCOV
261
    if (log.isDebugEnabled()) {
×
UNCOV
262
      log.debug("Extracted JAR URL: " + jarUrl);
×
263
    }
264

265
    // Try to open and test it
266
    try {
UNCOV
267
      URL testUrl = new URL(jarUrl.toString());
×
268
      if (isJar(testUrl)) {
×
269
        return testUrl;
×
270
      }
271
      // WebLogic fix: check if the URL's file exists in the filesystem.
272
      if (log.isDebugEnabled()) {
×
UNCOV
273
        log.debug("Not a JAR: " + jarUrl);
×
274
      }
275
      jarUrl.replace(0, jarUrl.length(), testUrl.getFile());
×
UNCOV
276
      File file = new File(jarUrl.toString());
×
277

278
      // File name might be URL-encoded
279
      if (!file.exists()) {
×
280
        file = new File(URLEncoder.encode(jarUrl.toString(), StandardCharsets.UTF_8));
×
281
      }
282

283
      if (file.exists()) {
×
284
        if (log.isDebugEnabled()) {
×
285
          log.debug("Trying real file: " + file.getAbsolutePath());
×
286
        }
287
        testUrl = file.toURI().toURL();
×
288
        if (isJar(testUrl)) {
×
289
          return testUrl;
×
290
        }
291
      }
292
    } catch (MalformedURLException e) {
×
293
      log.warn("Invalid JAR URL: " + jarUrl);
×
294
    }
×
295

296
    if (log.isDebugEnabled()) {
×
297
      log.debug("Not a JAR: " + jarUrl);
×
298
    }
299
    return null;
×
300
  }
301

302
  /**
303
   * Converts a Java package name to a path that can be looked up with a call to
304
   * {@link ClassLoader#getResources(String)}.
305
   *
306
   * @param packageName
307
   *          The Java package name to convert to a path
308
   *
309
   * @return the package path
310
   */
311
  protected String getPackagePath(String packageName) {
312
    return packageName == null ? null : packageName.replace('.', '/');
×
313
  }
314

315
  /**
316
   * Returns true if the resource located at the given URL is a JAR file.
317
   *
318
   * @param url
319
   *          The URL of the resource to test.
320
   *
321
   * @return true, if is jar
322
   */
323
  protected boolean isJar(URL url) {
324
    return isJar(url, new byte[JAR_MAGIC.length]);
1✔
325
  }
326

327
  /**
328
   * Returns true if the resource located at the given URL is a JAR file.
329
   *
330
   * @param url
331
   *          The URL of the resource to test.
332
   * @param buffer
333
   *          A buffer into which the first few bytes of the resource are read. The buffer must be at least the size of
334
   *          {@link #JAR_MAGIC}. (The same buffer may be reused for multiple calls as an optimization.)
335
   *
336
   * @return true, if is jar
337
   */
338
  protected boolean isJar(URL url, byte[] buffer) {
339
    try (InputStream is = url.openStream()) {
1✔
340
      is.read(buffer, 0, JAR_MAGIC.length);
1✔
341
      if (Arrays.equals(buffer, JAR_MAGIC)) {
1!
342
        if (log.isDebugEnabled()) {
×
343
          log.debug("Found JAR: " + url);
×
344
        }
345
        return true;
×
346
      }
347
    } catch (Exception e) {
×
348
      // Failure to read the stream means this is not a JAR
349
    }
1✔
350

351
    return false;
1✔
352
  }
353
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc