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 org.apache.felix.moduleloader.*; 022 import java.io.*; 023 import java.util.*; 024 import org.apache.felix.framework.Logger; 025 import org.apache.felix.framework.util.FelixConstants; 026 import org.apache.felix.framework.util.Util; 027 import org.osgi.framework.Constants; 028 029 public class DirectoryContent implements IContent 030 { 031 private static final int BUFSIZE = 4096; 032 private static final transient String EMBEDDED_DIRECTORY = "-embedded"; 033 private static final transient String LIBRARY_DIRECTORY = "-lib"; 034 035 private final Logger m_logger; 036 private final Map m_configMap; 037 private final Object m_revisionLock; 038 private final File m_rootDir; 039 private final File m_dir; 040 private Map m_nativeLibMap; 041 042 public DirectoryContent(Logger logger, Map configMap, Object revisionLock, 043 File rootDir, File dir) 044 { 045 m_logger = logger; 046 m_configMap = configMap; 047 m_revisionLock = revisionLock; 048 m_rootDir = rootDir; 049 m_dir = dir; 050 } 051 052 public void close() 053 { 054 // Nothing to clean up. 055 } 056 057 public synchronized boolean hasEntry(String name) throws IllegalStateException 058 { 059 if ((name.length() > 0) && (name.charAt(0) == '/')) 060 { 061 name = name.substring(1); 062 } 063 064 return new File(m_dir, name).exists(); 065 } 066 067 public synchronized Enumeration getEntries() 068 { 069 // Wrap entries enumeration to filter non-matching entries. 070 Enumeration e = new EntriesEnumeration(m_dir); 071 072 // Spec says to return null if there are no entries. 073 return (e.hasMoreElements()) ? e : null; 074 } 075 076 public synchronized byte[] getEntryAsBytes(String name) throws IllegalStateException 077 { 078 if ((name.length() > 0) && (name.charAt(0) == '/')) 079 { 080 name = name.substring(1); 081 } 082 083 // Get the embedded resource. 084 InputStream is = null; 085 ByteArrayOutputStream baos = null; 086 087 try 088 { 089 is = new BufferedInputStream(new FileInputStream(new File(m_dir, name))); 090 baos = new ByteArrayOutputStream(BUFSIZE); 091 byte[] buf = new byte[BUFSIZE]; 092 int n = 0; 093 while ((n = is.read(buf, 0, buf.length)) >= 0) 094 { 095 baos.write(buf, 0, n); 096 } 097 return baos.toByteArray(); 098 099 } 100 catch (Exception ex) 101 { 102 return null; 103 } 104 finally 105 { 106 try 107 { 108 if (baos != null) baos.close(); 109 } 110 catch (Exception ex) 111 { 112 } 113 try 114 { 115 if (is != null) is.close(); 116 } 117 catch (Exception ex) 118 { 119 } 120 } 121 } 122 123 public synchronized InputStream getEntryAsStream(String name) 124 throws IllegalStateException, IOException 125 { 126 if ((name.length() > 0) && (name.charAt(0) == '/')) 127 { 128 name = name.substring(1); 129 } 130 131 return new FileInputStream(new File(m_dir, name)); 132 } 133 134 public synchronized IContent getEntryAsContent(String entryName) 135 { 136 // If the entry name refers to the content itself, then 137 // just return it immediately. 138 if (entryName.equals(FelixConstants.CLASS_PATH_DOT)) 139 { 140 return new DirectoryContent(m_logger, m_configMap, m_revisionLock, m_rootDir, m_dir); 141 } 142 143 // Remove any leading slash, since all bundle class path 144 // entries are relative to the root of the bundle. 145 entryName = (entryName.startsWith("/")) ? entryName.substring(1) : entryName; 146 147 // Any embedded JAR files will be extracted to the embedded directory. 148 File embedDir = new File(m_rootDir, m_dir.getName() + EMBEDDED_DIRECTORY); 149 150 // Determine if the entry is an emdedded JAR file or 151 // directory in the bundle JAR file. Ignore any entries 152 // that do not exist per the spec. 153 File file = new File(m_dir, entryName); 154 if (BundleCache.getSecureAction().isFileDirectory(file)) 155 { 156 return new DirectoryContent(m_logger, m_configMap, m_revisionLock, m_rootDir, file); 157 } 158 else if (BundleCache.getSecureAction().fileExists(file) 159 && entryName.endsWith(".jar")) 160 { 161 File extractDir = new File(embedDir, 162 (entryName.lastIndexOf('/') >= 0) 163 ? entryName.substring(0, entryName.lastIndexOf('/')) 164 : entryName); 165 synchronized (m_revisionLock) 166 { 167 if (!BundleCache.getSecureAction().fileExists(extractDir)) 168 { 169 if (!BundleCache.getSecureAction().mkdirs(extractDir)) 170 { 171 m_logger.log( 172 Logger.LOG_ERROR, 173 "Unable to extract embedded directory."); 174 } 175 } 176 } 177 return new JarContent(m_logger, m_configMap, m_revisionLock, extractDir, file, null); 178 } 179 180 // The entry could not be found, so return null. 181 return null; 182 } 183 184 // TODO: This will need to consider security. 185 public synchronized String getEntryAsNativeLibrary(String entryName) 186 { 187 // Return result. 188 String result = null; 189 190 // Remove any leading slash, since all bundle class path 191 // entries are relative to the root of the bundle. 192 entryName = (entryName.startsWith("/")) ? entryName.substring(1) : entryName; 193 194 // Any embedded native library files will be extracted to the lib directory. 195 File libDir = new File(m_rootDir, m_dir.getName() + LIBRARY_DIRECTORY); 196 197 // The entry must exist and refer to a file, not a directory, 198 // since we are expecting it to be a native library. 199 File entryFile = new File(m_dir, entryName); 200 if (BundleCache.getSecureAction().fileExists(entryFile) 201 && !BundleCache.getSecureAction().isFileDirectory(entryFile)) 202 { 203 // Extracting the embedded native library file impacts all other 204 // existing contents for this revision, so we have to grab the 205 // revision lock first before trying to extract the embedded JAR 206 // file to avoid a race condition. 207 synchronized (m_revisionLock) 208 { 209 // Since native libraries cannot be shared, we must extract a 210 // separate copy per request, so use the request library counter 211 // as part of the extracted path. 212 if (m_nativeLibMap == null) 213 { 214 m_nativeLibMap = new HashMap(); 215 } 216 Integer libCount = (Integer) m_nativeLibMap.get(entryName); 217 // Either set or increment the library count. 218 libCount = (libCount == null) ? new Integer(0) : new Integer(libCount.intValue() + 1); 219 m_nativeLibMap.put(entryName, libCount); 220 File libFile = new File( 221 libDir, libCount.toString() + File.separatorChar + entryName); 222 223 if (!BundleCache.getSecureAction().fileExists(libFile)) 224 { 225 if (!BundleCache.getSecureAction().fileExists(libFile.getParentFile()) 226 && !BundleCache.getSecureAction().mkdirs(libFile.getParentFile())) 227 { 228 m_logger.log( 229 Logger.LOG_ERROR, 230 "Unable to create library directory."); 231 } 232 else 233 { 234 InputStream is = null; 235 236 try 237 { 238 is = new BufferedInputStream( 239 new FileInputStream(entryFile), 240 BundleCache.BUFSIZE); 241 if (is == null) 242 { 243 throw new IOException("No input stream: " + entryName); 244 } 245 246 // Create the file. 247 BundleCache.copyStreamToFile(is, libFile); 248 249 // Perform exec permission command on extracted library 250 // if one is configured. 251 String command = (String) m_configMap.get( 252 Constants.FRAMEWORK_EXECPERMISSION); 253 if (command != null) 254 { 255 Properties props = new Properties(); 256 props.setProperty("abspath", libFile.toString()); 257 command = Util.substVars(command, "command", null, props); 258 Process p = BundleCache.getSecureAction().exec(command); 259 p.waitFor(); 260 } 261 262 // Return the path to the extracted native library. 263 result = BundleCache.getSecureAction().getAbsolutePath(libFile); 264 } 265 catch (Exception ex) 266 { 267 m_logger.log( 268 Logger.LOG_ERROR, 269 "Extracting native library.", ex); 270 } 271 finally 272 { 273 try 274 { 275 if (is != null) is.close(); 276 } 277 catch (IOException ex) 278 { 279 // Not much we can do. 280 } 281 } 282 } 283 } 284 else 285 { 286 // Return the path to the extracted native library. 287 result = BundleCache.getSecureAction().getAbsolutePath(libFile); 288 } 289 } 290 } 291 292 return result; 293 } 294 295 public String toString() 296 { 297 return "DIRECTORY " + m_dir; 298 } 299 300 private static class EntriesEnumeration implements Enumeration 301 { 302 private File m_dir = null; 303 private File[] m_children = null; 304 private int m_counter = 0; 305 306 public EntriesEnumeration(File dir) 307 { 308 m_dir = dir; 309 m_children = listFilesRecursive(m_dir); 310 } 311 312 public boolean hasMoreElements() 313 { 314 return (m_children != null) && (m_counter < m_children.length); 315 } 316 317 public Object nextElement() 318 { 319 if ((m_children == null) || (m_counter >= m_children.length)) 320 { 321 throw new NoSuchElementException("No more entry paths."); 322 } 323 324 // Convert the file separator character to slashes. 325 String abs = m_children[m_counter].getAbsolutePath() 326 .replace(File.separatorChar, '/'); 327 328 // Remove the leading path of the reference directory, since the 329 // entry paths are supposed to be relative to the root. 330 StringBuffer sb = new StringBuffer(abs); 331 sb.delete(0, m_dir.getAbsolutePath().length() + 1); 332 // Add a '/' to the end of directory entries. 333 if (m_children[m_counter].isDirectory()) 334 { 335 sb.append('/'); 336 } 337 m_counter++; 338 return sb.toString(); 339 } 340 341 public File[] listFilesRecursive(File dir) 342 { 343 File[] children = dir.listFiles(); 344 File[] combined = children; 345 for (int i = 0; i < children.length; i++) 346 { 347 if (children[i].isDirectory()) 348 { 349 File[] grandchildren = listFilesRecursive(children[i]); 350 if (grandchildren.length > 0) 351 { 352 File[] tmp = new File[combined.length + grandchildren.length]; 353 System.arraycopy(combined, 0, tmp, 0, combined.length); 354 System.arraycopy(grandchildren, 0, tmp, combined.length, grandchildren.length); 355 combined = tmp; 356 } 357 } 358 } 359 return combined; 360 } 361 } 362 }