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 }