001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.felix.framework.cache;
020
021 import java.io.BufferedInputStream;
022 import java.io.ByteArrayOutputStream;
023 import java.io.File;
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.util.Enumeration;
027 import java.util.HashMap;
028 import java.util.Map;
029 import java.util.Properties;
030 import java.util.zip.ZipEntry;
031 import org.apache.felix.framework.Logger;
032 import org.apache.felix.framework.util.FelixConstants;
033 import org.apache.felix.framework.util.JarFileX;
034 import org.apache.felix.framework.util.Util;
035 import org.apache.felix.moduleloader.IContent;
036 import org.osgi.framework.Constants;
037
038 public class JarContent implements IContent
039 {
040 private static final int BUFSIZE = 4096;
041 private static final transient String EMBEDDED_DIRECTORY = "-embedded";
042 private static final transient String LIBRARY_DIRECTORY = "-lib";
043
044 private final Logger m_logger;
045 private final Map m_configMap;
046 private final Object m_revisionLock;
047 private final File m_rootDir;
048 private final File m_file;
049 private final JarFileX m_jarFile;
050 private final boolean m_isJarFileOwner;
051 private Map m_nativeLibMap;
052
053 public JarContent(Logger logger, Map configMap, Object revisionLock, File rootDir,
054 File file, JarFileX jarFile)
055 {
056 m_logger = logger;
057 m_configMap = configMap;
058 m_revisionLock = revisionLock;
059 m_rootDir = rootDir;
060 m_file = file;
061 m_jarFile = (jarFile == null) ? openJarFile(m_file) : jarFile;
062 m_isJarFileOwner = (jarFile == null);
063 }
064
065 protected void finalize()
066 {
067 close();
068 }
069
070 public void close()
071 {
072 try
073 {
074 if (m_isJarFileOwner)
075 {
076 m_jarFile.close();
077 }
078 }
079 catch (Exception ex)
080 {
081 m_logger.log(
082 Logger.LOG_ERROR,
083 "JarContent: Unable to close JAR file.", ex);
084 }
085 }
086
087 public boolean hasEntry(String name) throws IllegalStateException
088 {
089 try
090 {
091 ZipEntry ze = m_jarFile.getEntry(name);
092 return ze != null;
093 }
094 catch (Exception ex)
095 {
096 return false;
097 }
098 finally
099 {
100 }
101 }
102
103 public Enumeration getEntries()
104 {
105 // Wrap entries enumeration to filter non-matching entries.
106 Enumeration e = new EntriesEnumeration(m_jarFile.entries());
107
108 // Spec says to return null if there are no entries.
109 return (e.hasMoreElements()) ? e : null;
110 }
111
112 public byte[] getEntryAsBytes(String name) throws IllegalStateException
113 {
114 // Get the embedded resource.
115 InputStream is = null;
116 ByteArrayOutputStream baos = null;
117
118 try
119 {
120 ZipEntry ze = m_jarFile.getEntry(name);
121 if (ze == null)
122 {
123 return null;
124 }
125 is = m_jarFile.getInputStream(ze);
126 if (is == null)
127 {
128 return null;
129 }
130 baos = new ByteArrayOutputStream(BUFSIZE);
131 byte[] buf = new byte[BUFSIZE];
132 int n = 0;
133 while ((n = is.read(buf, 0, buf.length)) >= 0)
134 {
135 baos.write(buf, 0, n);
136 }
137 return baos.toByteArray();
138
139 }
140 catch (Exception ex)
141 {
142 m_logger.log(
143 Logger.LOG_ERROR,
144 "JarContent: Unable to read bytes.", ex);
145 return null;
146 }
147 finally
148 {
149 try
150 {
151 if (baos != null) baos.close();
152 }
153 catch (Exception ex)
154 {
155 }
156 try
157 {
158 if (is != null) is.close();
159 }
160 catch (Exception ex)
161 {
162 }
163 }
164 }
165
166 public InputStream getEntryAsStream(String name)
167 throws IllegalStateException, IOException
168 {
169 // Get the embedded resource.
170 InputStream is = null;
171
172 try
173 {
174 ZipEntry ze = m_jarFile.getEntry(name);
175 if (ze == null)
176 {
177 return null;
178 }
179 is = m_jarFile.getInputStream(ze);
180 if (is == null)
181 {
182 return null;
183 }
184 }
185 catch (Exception ex)
186 {
187 return null;
188 }
189
190 return is;
191 }
192
193 public IContent getEntryAsContent(String entryName)
194 {
195 // If the entry name refers to the content itself, then
196 // just return it immediately.
197 if (entryName.equals(FelixConstants.CLASS_PATH_DOT))
198 {
199 return new JarContent(m_logger, m_configMap, m_revisionLock,
200 m_rootDir, m_file, m_jarFile);
201 }
202
203 // Remove any leading slash.
204 entryName = (entryName.startsWith("/")) ? entryName.substring(1) : entryName;
205
206 // Any embedded JAR files will be extracted to the embedded directory.
207 // Since embedded JAR file names may clash when extracting from multiple
208 // embedded JAR files, the embedded directory is per embedded JAR file.
209 File embedDir = new File(m_rootDir, m_file.getName() + EMBEDDED_DIRECTORY);
210
211 // Find the entry in the JAR file and create the
212 // appropriate content type for it.
213
214 // Determine if the entry is an emdedded JAR file or
215 // directory in the bundle JAR file. Ignore any entries
216 // that do not exist per the spec.
217 ZipEntry ze = m_jarFile.getEntry(entryName);
218 if ((ze != null) && ze.isDirectory())
219 {
220 File extractDir = new File(embedDir, entryName);
221
222 // Extracting an embedded directory file impacts all other existing
223 // contents for this revision, so we have to grab the revision
224 // lock first before trying to create a directory for an embedded
225 // directory to avoid a race condition.
226 synchronized (m_revisionLock)
227 {
228 if (!BundleCache.getSecureAction().fileExists(extractDir))
229 {
230 if (!BundleCache.getSecureAction().mkdirs(extractDir))
231 {
232 m_logger.log(
233 Logger.LOG_ERROR,
234 "Unable to extract embedded directory.");
235 }
236 }
237 }
238 return new ContentDirectoryContent(this, entryName);
239 }
240 else if ((ze != null) && ze.getName().endsWith(".jar"))
241 {
242 File extractJar = new File(embedDir, entryName);
243
244 // Extracting the embedded JAR file impacts all other existing
245 // contents for this revision, so we have to grab the revision
246 // lock first before trying to extract the embedded JAR file
247 // to avoid a race condition.
248 synchronized (m_revisionLock)
249 {
250 if (!BundleCache.getSecureAction().fileExists(extractJar))
251 {
252 try
253 {
254 extractEmbeddedJar(entryName);
255 }
256 catch (Exception ex)
257 {
258 m_logger.log(
259 Logger.LOG_ERROR,
260 "Unable to extract embedded JAR file.", ex);
261 }
262 }
263 }
264 return new JarContent(
265 m_logger, m_configMap, m_revisionLock,
266 extractJar.getParentFile(), extractJar, null);
267 }
268
269 // The entry could not be found, so return null.
270 return null;
271 }
272
273 // TODO: This will need to consider security.
274 public String getEntryAsNativeLibrary(String entryName)
275 {
276 // Return result.
277 String result = null;
278
279 // Remove any leading slash.
280 entryName = (entryName.startsWith("/")) ? entryName.substring(1) : entryName;
281
282 // Any embedded native libraries will be extracted to the lib directory.
283 // Since embedded library file names may clash when extracting from multiple
284 // embedded JAR files, the embedded lib directory is per embedded JAR file.
285 File libDir = new File(m_rootDir, m_file.getName() + LIBRARY_DIRECTORY);
286
287 // The entry name must refer to a file type, since it is
288 // a native library, not a directory.
289 ZipEntry ze = m_jarFile.getEntry(entryName);
290 if ((ze != null) && !ze.isDirectory())
291 {
292 // Extracting the embedded native library file impacts all other
293 // existing contents for this revision, so we have to grab the
294 // revision lock first before trying to extract the embedded JAR
295 // file to avoid a race condition.
296 synchronized (m_revisionLock)
297 {
298 // Since native libraries cannot be shared, we must extract a
299 // separate copy per request, so use the request library counter
300 // as part of the extracted path.
301 if (m_nativeLibMap == null)
302 {
303 m_nativeLibMap = new HashMap();
304 }
305 Integer libCount = (Integer) m_nativeLibMap.get(entryName);
306 // Either set or increment the library count.
307 libCount = (libCount == null) ? new Integer(0) : new Integer(libCount.intValue() + 1);
308 m_nativeLibMap.put(entryName, libCount);
309 File libFile = new File(
310 libDir, libCount.toString() + File.separatorChar + entryName);
311
312 if (!BundleCache.getSecureAction().fileExists(libFile))
313 {
314 if (!BundleCache.getSecureAction().fileExists(libFile.getParentFile())
315 && !BundleCache.getSecureAction().mkdirs(libFile.getParentFile()))
316 {
317 m_logger.log(
318 Logger.LOG_ERROR,
319 "Unable to create library directory.");
320 }
321 else
322 {
323 InputStream is = null;
324
325 try
326 {
327 is = new BufferedInputStream(
328 m_jarFile.getInputStream(ze),
329 BundleCache.BUFSIZE);
330 if (is == null)
331 {
332 throw new IOException("No input stream: " + entryName);
333 }
334
335 // Create the file.
336 BundleCache.copyStreamToFile(is, libFile);
337
338 // Perform exec permission command on extracted library
339 // if one is configured.
340 String command = (String) m_configMap.get(
341 Constants.FRAMEWORK_EXECPERMISSION);
342 if (command != null)
343 {
344 Properties props = new Properties();
345 props.setProperty("abspath", libFile.toString());
346 command = Util.substVars(command, "command", null, props);
347 Process p = BundleCache.getSecureAction().exec(command);
348 // We have to make sure we read stdout and stderr because
349 // otherwise we will block on certain unbuffered os's
350 // (like eg. windows)
351 Thread stdOut = new Thread(
352 new DevNullRunnable(p.getInputStream()));
353 Thread stdErr = new Thread(
354 new DevNullRunnable(p.getErrorStream()));
355 stdOut.setDaemon(true);
356 stdErr.setDaemon(true);
357 stdOut.start();
358 stdErr.start();
359 p.waitFor();
360 stdOut.join();
361 stdErr.join();
362 }
363
364 // Return the path to the extracted native library.
365 result = BundleCache.getSecureAction().getAbsolutePath(libFile);
366 }
367 catch (Exception ex)
368 {
369 m_logger.log(
370 Logger.LOG_ERROR,
371 "Extracting native library.", ex);
372 }
373 finally
374 {
375 try
376 {
377 if (is != null) is.close();
378 }
379 catch (IOException ex)
380 {
381 // Not much we can do.
382 }
383 }
384 }
385 }
386 else
387 {
388 // Return the path to the extracted native library.
389 result = BundleCache.getSecureAction().getAbsolutePath(libFile);
390 }
391 }
392 }
393
394 return result;
395 }
396
397 public String toString()
398 {
399 return "JAR " + m_file.getPath();
400 }
401
402 public File getFile()
403 {
404 return m_file;
405 }
406
407 /**
408 * This method extracts an embedded JAR file from the bundle's
409 * JAR file.
410 * @param id the identifier of the bundle that owns the embedded JAR file.
411 * @param jarPath the path to the embedded JAR file inside the bundle JAR file.
412 **/
413 private void extractEmbeddedJar(String jarPath)
414 throws Exception
415 {
416 // Remove leading slash if present.
417 jarPath = (jarPath.length() > 0) && (jarPath.charAt(0) == '/')
418 ? jarPath.substring(1) : jarPath;
419
420 // Any embedded JAR files will be extracted to the embedded directory.
421 // Since embedded JAR file names may clash when extracting from multiple
422 // embedded JAR files, the embedded directory is per embedded JAR file.
423 File embedDir = new File(m_rootDir, m_file.getName() + EMBEDDED_DIRECTORY);
424 File jarFile = new File(embedDir, jarPath);
425
426 if (!BundleCache.getSecureAction().fileExists(jarFile))
427 {
428 InputStream is = null;
429 try
430 {
431 // Make sure class path entry is a JAR file.
432 ZipEntry ze = m_jarFile.getEntry(jarPath);
433 if (ze == null)
434 {
435 return;
436 }
437 // If the zip entry is a directory, then ignore it since
438 // we don't need to extact it; otherwise, it points to an
439 // embedded JAR file, so extract it.
440 else if (!ze.isDirectory())
441 {
442 // Make sure that the embedded JAR's parent directory exists;
443 // it may be in a sub-directory.
444 File jarDir = jarFile.getParentFile();
445 if (!BundleCache.getSecureAction().fileExists(jarDir))
446 {
447 if (!BundleCache.getSecureAction().mkdirs(jarDir))
448 {
449 throw new IOException("Unable to create embedded JAR directory.");
450 }
451 }
452
453 // Extract embedded JAR into its directory.
454 is = new BufferedInputStream(m_jarFile.getInputStream(ze), BundleCache.BUFSIZE);
455 if (is == null)
456 {
457 throw new IOException("No input stream: " + jarPath);
458 }
459 // Copy the file.
460 BundleCache.copyStreamToFile(is, jarFile);
461 }
462 }
463 finally
464 {
465 if (is != null) is.close();
466 }
467 }
468 }
469
470 private static JarFileX openJarFile(File file) throws RuntimeException
471 {
472 try
473 {
474 return BundleCache.getSecureAction().openJAR(file, false);
475 }
476 catch (IOException ex)
477 {
478 throw new RuntimeException(
479 "Unable to open JAR file, probably deleted: " + ex.getMessage());
480 }
481 }
482
483 private static class EntriesEnumeration implements Enumeration
484 {
485 private Enumeration m_enumeration = null;
486
487 public EntriesEnumeration(Enumeration enumeration)
488 {
489 m_enumeration = enumeration;
490 }
491
492 public boolean hasMoreElements()
493 {
494 return m_enumeration.hasMoreElements();
495 }
496
497 public Object nextElement()
498 {
499 return ((ZipEntry) m_enumeration.nextElement()).getName();
500 }
501 }
502
503 private static class DevNullRunnable implements Runnable
504 {
505 private final InputStream m_in;
506
507 public DevNullRunnable(InputStream in)
508 {
509 m_in = in;
510 }
511
512 public void run()
513 {
514 try
515 {
516 try
517 {
518 while (m_in.read() != -1){}
519 }
520 finally
521 {
522 m_in.close();
523 }
524 }
525 catch (Exception ex)
526 {
527 // Not much we can do - maybe we should log it?
528 }
529 }
530 }
531 }