org.apache.sling.osgi.obr.Repository.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sling.osgi.obr.Repository.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.sling.osgi.obr;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.Deflater;

import org.apache.commons.io.IOUtils;
import org.osgi.framework.Version;

/**
 * The <code>Repository</code> represents the collection of all bundles in the
 * repository. It is initialized with the location of the bundle store and may
 * be accessed by different accessors.
 *
 * @author fmeschbe
 * @version $Rev:25139 $, $Date:2007-02-12 16:37:26 +0100 (Mo, 12 Feb 2007) $
 */
public class Repository {

    /**
     * Pseudo bundle category to which all bundles belong by definition (value
     * is "[All Bundles]").
     */
    public static final String CATEGORY_ALL_BUNDLES = "[All Bundles]";

    /**
     * Pseudo bundle category to which all bundles belong which do not have a
     * <code>Bundle-Category</code> header in their bundle manifast (value is
     * "[None]").
     */
    public static final String CATEGORY_NONE = "[None]";

    private static final String REPO_PROPERTIES = "repository.properties";

    private static final String PROP_REPO_NAME = "org.apache.sling.osgi.obr.name";

    private static final String PROP_PREFIX_BUNDLE = "org.apache.sling.osgi.obr.bundle.";

    // 20060817025624.330
    private static final DateFormat REPO_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss.SSS");

    // 20060817025624
    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");

    private File repoLocation;

    private File repoPropertiesFile;

    private long repoLastModified;

    private String repoName;

    // sorted set of bundle URIs
    private SortedSet bundles;

    // true if a reload is requird in ensureLoaded
    private boolean needReload;

    // map of resource sets indexed by category
    private SortedMap resourcesByCategory;

    // list of resources
    private List resources;

    // map of resources indexed by file name
    private SortedMap resourcesByFileName;

    public Repository(String defaultRepoName, File repoLocation) throws IOException {
        this.repoName = (defaultRepoName != null) ? defaultRepoName : "untitled";
        this.repoLocation = repoLocation;
        this.repoPropertiesFile = new File(repoLocation, REPO_PROPERTIES);

        this.loadProperties();
        this.needReload = true;
    }

    public String getName() {
        return this.repoName;
    }

    public long getLastModified() {
        return this.repoLastModified;
    }

    public String getLastModifiedFormatted() {
        synchronized (REPO_DATE_FORMAT) {
            return REPO_DATE_FORMAT.format(new Date(this.getLastModified()));
        }
    }

    public void addResource(InputStream bundleStream) throws IOException {
        File bundle = null;
        try {
            // spool the bundle (throw if invalid)
            bundle = this.spoolModified(bundleStream);

            // Check bundle by trying to read the manifest, throws in
            // case of problems, never returns null
            Resource.create(bundle);

            this.bundles.add(bundle.toURI().toString());
            this.needReload = true;

            this.saveProperties();
        } catch (IOException ioe) {
            // bundle seems invalid, remove the file
            if (bundle != null) {
                bundle.delete();
            }
            throw ioe;
        }
    }

    public Resource getResource(String bundleName) {
        this.ensureLoaded();

        // try the bundleName as resource file name first
        Resource res = (Resource) this.resourcesByFileName.get(bundleName);

        // not found, assume a bundle symbolic name
        if (res == null) {
            for (Iterator ri = this.getResourcesById(); ri.hasNext();) {
                Resource testRes = (Resource) ri.next();
                if (bundleName.equals(testRes.getSymbolicName())) {
                    if (res == null || testRes.getVersion().compareTo(res.getVersion()) > 0) {
                        res = testRes;
                    }
                }
            }
        }

        // return what we found (best match or nothing at all)
        return res;
    }

    public Iterator getResourcesById() {
        this.ensureLoaded();
        return this.resources.iterator();
    }

    public Iterator getResourcesByCategory(String categoryName) {
        this.ensureLoaded();

        Set resources = (Set) this.resourcesByCategory.get(categoryName);
        if (resources != null) {
            return resources.iterator();
        }
        return Collections.EMPTY_SET.iterator();
    }

    public void removeResource(String bundleName) throws IOException {
        this.needReload = true;
        File bundleFile = new File(this.repoLocation, bundleName);
        this.bundles.remove(bundleFile.toURI().toString());
        bundleFile.delete();
        this.saveProperties();
    }

    /**
     * Returns a sorted list of all categories to which the bundles stored in
     * this repository belong. This list also contains the special category
     * {@link #CATEGORY_ALL_BUNDLES}.
     *
     * @param comparator The <code>Comparator</code> to use to sort the bundle
     *            list. If <code>null</code> the natural ordering is used.
     * @return The sorted list of bundle categories of the bundles.
     */
    public List getBundleCategories(Comparator comparator) {
        this.ensureLoaded();

        List list = new ArrayList(this.resourcesByCategory.keySet());
        if (comparator != null) {
            Collections.sort(list, comparator);
        }
        return list;
    }

    private void ensureLoaded() {
        if (!this.needReload) {
            return;
        }

        // load the resources
        this.resources = new ArrayList();
        this.resourcesByFileName = new TreeMap();
        this.resourcesByCategory = new TreeMap();

        Iterator bi = this.bundles.iterator();
        for (int id = 0; bi.hasNext(); id++) {
            String mapping = (String) bi.next();
            try {
                URL bundle = new URL(mapping);
                Resource res = Resource.create(bundle);
                res.setId(String.valueOf(id));

                this.resources.add(res);
                this.resourcesByFileName.put(res.getResourceName(), res);

                this.registerResourceByCategory(CATEGORY_ALL_BUNDLES, res);
                if (res.getCategories().isEmpty()) {
                    this.registerResourceByCategory(CATEGORY_NONE, res);
                } else {
                    for (Iterator ci = res.getCategories().iterator(); ci.hasNext();) {
                        this.registerResourceByCategory(ci.next(), res);
                    }
                }

            } catch (MalformedURLException mue) {
                // TODO: log, but actually not expected ...
            } catch (IOException ioe) {
                // TODO: log
            }
        }

        // prevent further "reloads" until change
        this.needReload = false;
    }

    private void registerResourceByCategory(Object category, Resource res) {
        SortedSet resources = (SortedSet) this.resourcesByCategory.get(category);
        if (resources == null) {
            resources = new TreeSet();
            this.resourcesByCategory.put(category, resources);
        }
        resources.add(res);
    }

    private void loadProperties() throws IOException {
        Properties props = new Properties();
        if (this.repoPropertiesFile.exists()) {
            InputStream ins = null;
            try {
                ins = new FileInputStream(this.repoPropertiesFile);
                props.load(ins);
            } finally {
                if (ins != null) {
                    try {
                        ins.close();
                    } catch (IOException ioe) {
                        // ignore
                    }
                }
            }
            this.repoLastModified = this.repoPropertiesFile.lastModified();
        }

        TreeSet bundles = new TreeSet();
        for (Iterator ei = props.entrySet().iterator(); ei.hasNext();) {
            Map.Entry entry = (Map.Entry) ei.next();
            String key = (String) entry.getKey();
            if (key.startsWith(PROP_PREFIX_BUNDLE)) {
                bundles.add(entry.getValue());
            }
        }
        this.bundles = bundles;

        String repoName = props.getProperty(PROP_REPO_NAME);
        if (repoName == null) {
            // save the properties immediately with the default repository Name
            this.saveProperties();
        } else {
            // use the configured name as the repository Name
            this.repoName = repoName;
        }
    }

    private void saveProperties() throws IOException {
        // prepare the properties to write
        Properties props = new Properties();
        props.setProperty(PROP_REPO_NAME, this.repoName);
        Iterator bi = this.bundles.iterator();
        for (int i = 0; bi.hasNext(); i++) {
            props.setProperty(PROP_PREFIX_BUNDLE + i, (String) bi.next());
        }

        // write the properties
        OutputStream out = null;
        try {
            out = new FileOutputStream(this.repoPropertiesFile);
            props.store(out, null);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException ioe) {
                    // ignore
                }
            }
        }

        // update the last modified value
        this.repoLastModified = this.repoPropertiesFile.lastModified();
    }

    static void spool(InputStream ins, OutputStream out) throws IOException {
        byte[] buf = new byte[8192];
        int rd;
        while ((rd = ins.read(buf)) > 0) {
            out.write(buf, 0, rd);
        }
    }

    File spoolModified(InputStream ins) throws IOException {
        JarInputStream jis = new JarInputStream(ins);

        // immediately handle the manifest
        JarOutputStream jos;
        Manifest manifest = jis.getManifest();
        if (manifest == null) {
            throw new IOException("Missing Manifest !");
        }

        String symbolicName = manifest.getMainAttributes().getValue("Bundle-SymbolicName");
        if (symbolicName == null || symbolicName.length() == 0) {
            throw new IOException("Missing Symbolic Name in Manifest !");
        }

        String version = manifest.getMainAttributes().getValue("Bundle-Version");
        Version v = Version.parseVersion(version);
        if (v.getQualifier().indexOf("SNAPSHOT") >= 0) {
            String tStamp;
            synchronized (DATE_FORMAT) {
                tStamp = DATE_FORMAT.format(new Date());
            }
            version = v.getMajor() + "." + v.getMinor() + "." + v.getMicro() + "."
                    + v.getQualifier().replaceAll("SNAPSHOT", tStamp);
            manifest.getMainAttributes().putValue("Bundle-Version", version);
        }

        File bundle = new File(this.repoLocation, symbolicName + "-" + v + ".jar");
        OutputStream out = null;
        try {
            out = new FileOutputStream(bundle);
            jos = new JarOutputStream(out, manifest);

            jos.setMethod(JarOutputStream.DEFLATED);
            jos.setLevel(Deflater.BEST_COMPRESSION);

            JarEntry entryIn = jis.getNextJarEntry();
            while (entryIn != null) {
                JarEntry entryOut = new JarEntry(entryIn.getName());
                entryOut.setTime(entryIn.getTime());
                entryOut.setComment(entryIn.getComment());
                jos.putNextEntry(entryOut);
                if (!entryIn.isDirectory()) {
                    spool(jis, jos);
                }
                jos.closeEntry();
                jis.closeEntry();
                entryIn = jis.getNextJarEntry();
            }

            // close the JAR file now to force writing
            jos.close();

        } finally {
            IOUtils.closeQuietly(out);
        }

        return bundle;
    }

}