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 }