001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Bogdan Stefanescu
018 *     Florent Guillaume
019 */
020
021package org.nuxeo.osgi;
022
023import java.io.File;
024import java.io.FileFilter;
025import java.io.FileInputStream;
026import java.io.IOException;
027import java.net.MalformedURLException;
028import java.net.URL;
029import java.util.ArrayList;
030import java.util.Collection;
031import java.util.Enumeration;
032import java.util.List;
033import java.util.jar.Attributes;
034import java.util.jar.Manifest;
035
036import org.nuxeo.common.utils.FileUtils;
037import org.nuxeo.common.utils.StringUtils;
038import org.nuxeo.osgi.util.CompoundEnumeration;
039import org.nuxeo.osgi.util.EntryFilter;
040import org.nuxeo.osgi.util.FileIterator;
041import org.osgi.framework.BundleException;
042import org.osgi.framework.Constants;
043
044/**
045 * A {@link BundleFile} that is backed by a filesystem directory, for use in test settings from Eclipse or maven.
046 */
047public class DirectoryBundleFile implements BundleFile {
048
049    public static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
050
051    protected final File file;
052
053    protected final List<File> files;
054
055    protected final Manifest mf;
056
057    public DirectoryBundleFile(File file) throws IOException {
058        this(file, null);
059    }
060
061    public DirectoryBundleFile(File file, Manifest mf) throws IOException {
062        this.file = file;
063        this.files = findFiles(file);
064        this.mf = mf == null ? findManifest() : mf;
065    }
066
067    protected List<File> findFiles(File file) {
068        List<File> files = new ArrayList<File>(2);
069        files.add(file);
070        if (file.getPath().endsWith("/bin")) {
071            // hack for Eclipse PDE development
072            files.add(file.getParentFile());
073        } else if (file.getPath().endsWith("/target/classes")) {
074            // hack for maven/tycho development
075            files.add(file.getParentFile().getParentFile());
076        }
077        return files;
078    }
079
080    private Enumeration<URL> createEnumeration(File root, final EntryFilter efilter, final boolean recurse) {
081        FileIterator it = new FileIterator(root, new FileFilter() {
082            @Override
083            public boolean accept(File pathname) {
084                if (pathname.isDirectory()) {
085                    return recurse;
086                }
087                return efilter.match(pathname.getName());
088            }
089        });
090        it.setSkipDirs(true);
091        return FileIterator.asUrlEnumeration(it);
092    }
093
094    @SuppressWarnings("unchecked")
095    @Override
096    public Enumeration<URL> findEntries(String name, String pattern, boolean recurse) {
097        EntryFilter efilter = EntryFilter.newFilter(pattern);
098        if (files.size() == 1) {
099            return createEnumeration(new File(file, name), efilter, recurse);
100        } else {
101            Enumeration<URL>[] enums = new Enumeration[files.size()];
102            int i = 0;
103            for (File f : files) {
104                enums[i++] = createEnumeration(new File(f, name), efilter, recurse);
105            }
106            return new CompoundEnumeration<URL>(enums);
107        }
108    }
109
110    @Override
111    public URL getEntry(String name) {
112        for (File file : files) {
113            File entry = new File(file, name);
114            if (entry.exists()) {
115                try {
116                    return entry.toURI().toURL();
117                } catch (MalformedURLException e) {
118                    // ignore
119                }
120            }
121        }
122        return null;
123    }
124
125    @Override
126    public Enumeration<String> getEntryPaths(String path) {
127        throw new UnsupportedOperationException("The operation BundleFile.geEntryPaths() is not yet implemented");
128    }
129
130    @Override
131    public File getFile() {
132        return file;
133    }
134
135    @Override
136    public String getFileName() {
137        return file.getName();
138    }
139
140    @Override
141    public String getLocation() {
142        return file.getPath();
143    }
144
145    @Override
146    public Manifest getManifest() {
147        return mf;
148    }
149
150    protected Manifest findManifest() throws IOException {
151        for (File file : files) {
152            File entry = new File(file, MANIFEST_PATH);
153            if (entry.exists()) {
154                FileInputStream fis = new FileInputStream(entry);
155                try {
156                    return new Manifest(fis);
157                } finally {
158                    fis.close();
159                }
160            }
161        }
162        String paths = StringUtils.join(files.toArray(new Object[files.size()]), ", ");
163        throw new IOException(String.format("Could not find a file '%s' in paths: %s", MANIFEST_PATH, paths));
164    }
165
166    @Override
167    public Collection<BundleFile> getNestedBundles(File tmpDir) throws IOException {
168        Attributes attrs = mf.getMainAttributes();
169        String cp = attrs.getValue(Constants.BUNDLE_CLASSPATH);
170        if (cp == null) {
171            cp = attrs.getValue("Class-Path");
172        }
173        if (cp == null) {
174            return null;
175        }
176        String[] paths = StringUtils.split(cp, ',', true);
177        List<BundleFile> nested = new ArrayList<BundleFile>();
178        for (String path : paths) {
179            File nestedBundle = new File(file, path);
180            if (nestedBundle.isDirectory()) {
181                nested.add(new DirectoryBundleFile(nestedBundle));
182            } else {
183                nested.add(new JarBundleFile(nestedBundle));
184            }
185        }
186        return nested;
187    }
188
189    @Override
190    public Collection<BundleFile> findNestedBundles(File tmpDir) throws IOException {
191        List<BundleFile> nested = new ArrayList<BundleFile>();
192        File[] files = FileUtils.findFiles(file, "*.jar", true);
193        for (File jar : files) {
194            if (jar.isDirectory()) {
195                nested.add(new DirectoryBundleFile(jar));
196            } else {
197                nested.add(new JarBundleFile(jar));
198            }
199        }
200        return nested;
201    }
202
203    @Override
204    public String getSymbolicName() {
205        String value = mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
206        return value == null ? null : BundleManifestReader.removePropertiesFromHeaderValue(value);
207    }
208
209    @Override
210    public URL getURL() {
211        try {
212            return file.toURI().toURL();
213        } catch (MalformedURLException e) {
214            return null;
215        }
216    }
217
218    @Override
219    public boolean isDirectory() {
220        return true;
221    }
222
223    @Override
224    public boolean isJar() {
225        return false;
226    }
227
228    @Override
229    public String toString() {
230        return getLocation();
231    }
232
233    public static void main(String[] args) throws Exception {
234        DirectoryBundleFile bf = new DirectoryBundleFile(new File(
235                "/Users/bstefanescu/work/org.eclipse.ecr/plugins/org.eclipse.ecr.application/bin"));
236        Enumeration<URL> urls = bf.findEntries("META-INF", "*.txt", false);
237        while (urls.hasMoreElements()) {
238            System.out.println(urls.nextElement());
239        }
240    }
241
242    @Override
243    public void close(OSGiAdapter osgi) throws IOException {
244        return;
245    }
246
247}