org.bndtools.rt.repository.server.RepositoryResourceComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.bndtools.rt.repository.server.RepositoryResourceComponent.java

Source

/*******************************************************************************
 * Copyright (c) 2012 Neil Bartlett.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Neil Bartlett - initial API and implementation
 ******************************************************************************/
package org.bndtools.rt.repository.server;

import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.zip.GZIPInputStream;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

import org.osgi.framework.Constants;
import org.osgi.service.provisioning.ProvisioningService;

import aQute.bnd.annotation.component.Activate;
import aQute.bnd.annotation.component.Component;
import aQute.bnd.annotation.metatype.Meta;
import aQute.bnd.deployer.repository.LocalIndexedRepo;
import aQute.bnd.service.RepositoryPlugin;
import aQute.bnd.service.ResourceHandle;
import aQute.bnd.service.ResourceHandle.Location;
import aQute.bnd.service.Strategy;
import aQute.bnd.version.Version;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;

@Path("/")
@Component(name = "org.bndtools.rt.repository", provide = Object.class, immediate = true, properties = "osgi.rest.alias=/repo", designateFactory = RepositoryResourceComponent.Config.class)
public class RepositoryResourceComponent {

    interface Config {
        @Meta.AD
        String local();
    }

    private final LocalIndexedRepo repo = new LocalIndexedRepo();
    private final JsonFactory jsonFactory = new JsonFactory();

    private File storageDir;

    @Activate
    void activate(Map<String, String> configProps) throws Exception {
        repo.setProperties(configProps);
        storageDir = repo.getRoot().getCanonicalFile();
    }

    @GET
    @Path("bundles")
    @Produces(MediaType.APPLICATION_JSON)
    public Response listBundles(@Context UriInfo uriInfo) throws Exception {
        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
        List<String> patterns = queryParams.get("pattern");
        if (patterns == null || patterns.isEmpty())
            return Response.status(Status.BAD_REQUEST).entity("Bundle listing requires 'pattern' query parameter.")
                    .build();

        UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder().path("{bsn}");
        StringWriter writer = new StringWriter();
        JsonGenerator generator = jsonFactory.createJsonGenerator(writer);
        generator.writeStartArray();

        for (String pattern : patterns) {
            List<String> list = repo.list(pattern);
            for (String bsn : list) {
                generator.writeStartObject();
                generator.writeStringField("bsn", bsn);
                generator.writeStringField("href", uriBuilder.build(bsn).toString());
                generator.writeEndObject();
            }
        }
        generator.writeEndArray();
        generator.close();

        return Response.ok(writer.toString(), MediaType.APPLICATION_JSON).build();
    }

    @GET
    @Path("bundles/{bsn}/{version}")
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Response getBundleByBsnAndVersion(@PathParam("bsn") String bsn, @PathParam("version") String versionStr)
            throws Exception {
        ResourceHandle handle = repo.getHandle(bsn, versionStr, Strategy.EXACT, null);
        if (handle == null)
            return Response.status(Status.NOT_FOUND).build();

        UriBuilder uriBuilder = UriBuilder.fromResource(RepositoryResourceComponent.class).path("{bundlePath}");

        if (handle.getLocation() == Location.local) {
            String bundlePath = handle.request().getCanonicalPath();
            String prefix = storageDir.getAbsolutePath() + File.separatorChar;
            if (bundlePath.startsWith(prefix)) {
                bundlePath = bundlePath.substring(prefix.length());
                URI bundleUri = uriBuilder.build(bundlePath);

                return Response.seeOther(bundleUri).build();
            }
        }
        return Response.serverError().entity("Bundle is not available in the repository storage area").build();
    }

    @GET
    @Path("bundles/{bsn}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response listVersions(@Context UriInfo uriInfo, @PathParam("bsn") String bsn) throws Exception {
        SortedSet<Version> versions = repo.versions(bsn);

        UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder().path("{version}");

        StringWriter writer = new StringWriter();
        JsonGenerator generator = jsonFactory.createJsonGenerator(writer);
        generator.writeStartArray();
        for (Version version : versions) {
            generator.writeStartObject();
            generator.writeStringField("bsn", bsn);
            generator.writeStringField("version", version.toString());
            generator.writeStringField("href", uriBuilder.build(version).toString());
            generator.writeEndObject();
        }
        generator.writeEndArray();
        generator.close();

        return Response.ok(writer.toString(), MediaType.APPLICATION_JSON).build();
    }

    @GET
    @Path("{bsn}/{filename}")
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Response getBundleContext(@PathParam("bsn") String bsn, @PathParam("filename") String filename)
            throws Exception {
        File bundleFile = new File(storageDir, bsn + File.separatorChar + filename);
        if (!bundleFile.isFile())
            return Response.status(Status.NOT_FOUND).build();

        return Response.ok(new FileInputStream(bundleFile), MediaType.APPLICATION_OCTET_STREAM_TYPE).build();
    }

    @GET
    @Path("index")
    @Produces(MediaType.APPLICATION_XML)
    public Response getIndex(@Context HttpHeaders headers) throws Exception {
        List<URI> locations = repo.getIndexLocations();
        if (locations.isEmpty())
            return Response.serverError().entity("No index available").build();

        Response response;
        URI location = locations.get(0);
        if ("file".equals(location.getScheme())) {
            File file = new File(location);

            @SuppressWarnings("resource")
            FileInputStream indexStream = new FileInputStream(file);
            InputStream bufferedStream = indexStream.markSupported() ? indexStream
                    : new BufferedInputStream(indexStream);

            InputStream responseStream;
            if (isGZip(bufferedStream)) {
                responseStream = new GZIPInputStream(indexStream);
            } else {
                responseStream = bufferedStream;
            }
            response = Response.ok(responseStream, MediaType.APPLICATION_XML_TYPE).build();
        } else {
            response = Response.status(Status.SEE_OTHER).location(location).build();
        }
        return response;
    }

    private static boolean isGZip(InputStream bufferedStream) throws IOException {
        assert bufferedStream.markSupported();

        bufferedStream.mark(2);
        int magic = readUShort(bufferedStream);
        bufferedStream.reset();

        return magic == GZIPInputStream.GZIP_MAGIC;
    }

    private static int readUShort(InputStream in) throws IOException {
        int b = readUByte(in);
        return (readUByte(in) << 8) | b;
    }

    private static int readUByte(InputStream in) throws IOException {
        int b = in.read();
        if (b == -1) {
            throw new EOFException();
        }
        if (b < -1 || b > 255) {
            // Report on this.in, not argument in; see read{Header, Trailer}.
            throw new IOException(in.getClass().getName() + ".read() returned value out of range -1..255: " + b);
        }
        return b;
    }

    @POST
    @Path("bundles")
    @Consumes({ MediaType.APPLICATION_OCTET_STREAM, ProvisioningService.MIME_BUNDLE })
    public Response uploadBundle(@Context UriInfo uriInfo, InputStream stream) throws Exception {
        if (!repo.canWrite())
            return Response.status(Status.BAD_REQUEST).entity("Repository is not writeable").build();

        InputStream bufferedStream = stream.markSupported() ? stream : new BufferedInputStream(stream);
        Manifest manifest = readManifest(bufferedStream);

        Attributes mainAttribs = manifest.getMainAttributes();
        String bsn = mainAttribs.getValue(Constants.BUNDLE_SYMBOLICNAME);
        if (bsn == null)
            return Response.status(Status.BAD_REQUEST).entity("not a bundle").build();
        String versionStr = mainAttribs.getValue(Constants.BUNDLE_VERSION);
        Version version = versionStr != null ? new Version(versionStr) : Version.emptyVersion;

        URI bundleLocation = uriInfo.getAbsolutePathBuilder().path("{bsn}/{version}").build(bsn, version);

        repo.put(bufferedStream, RepositoryPlugin.DEFAULTOPTIONS);
        return Response.created(bundleLocation).build();
    }

    private static Manifest readManifest(InputStream stream) throws IOException {
        if (!stream.markSupported())
            throw new IOException("Stream must support mark/reset");

        stream.mark(100000);
        try {
            @SuppressWarnings("resource")
            JarInputStream jarStream = new JarInputStream(stream);
            return jarStream.getManifest();
        } finally {
            stream.reset();
        }
    }

}