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    }