/******************************************************************************* * Copyright (c) 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Jan S. Rellermeyer, IBM Research - initial API and implementation *******************************************************************************/ package org.osgi.impl.service.rest.resources; import java.util.ArrayList; import java.util.Collection; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.namespace.IdentityNamespace; import org.osgi.framework.startlevel.FrameworkStartLevel; import org.osgi.framework.wiring.BundleRevision; import org.osgi.impl.service.rest.PojoReflector; import org.osgi.impl.service.rest.RestService; import org.osgi.impl.service.rest.pojos.BundleExceptionPojo; import org.osgi.impl.service.rest.pojos.ServicePojo; import org.osgi.resource.Capability; import org.osgi.service.rest.RestApiExtension; import org.osgi.util.tracker.ServiceTracker; import org.restlet.data.MediaType; import org.restlet.data.Status; import org.restlet.representation.Representation; import org.restlet.representation.Variant; import org.restlet.resource.ServerResource; import org.w3c.dom.Document; /** * Abstract OSGi resource with the functionality to translate OSGi entities to * representations according to requested media types (variants). * * @author Jan S. Rellermeyer, IBM Research * * @param <T> the pojo base class which is reflected by the pojo reflector. */ public class AbstractOSGiResource<T> extends ServerResource { private static final String number = "([0-9]*)"; private static final String MT_JSON = "+json"; private static final String MT_XML = "+xml"; private static final String MT_BE = "application/org.osgi.bundleexception"; private static final MediaType MT_BE_JSON = new MediaType(MT_BE + MT_JSON); private static final MediaType MT_BE_XML = new MediaType(MT_BE + MT_XML); protected final static Representation SUCCESS = null; private final static Representation ERROR = null; private final PojoReflector<T> reflector; private final MediaType xmlMediaType; private final MediaType jsonMediaType; protected AbstractOSGiResource(final PojoReflector<T> reflector, final MediaType mediaType) { this.reflector = reflector; this.xmlMediaType = new MediaType(mediaType.toString() + MT_XML); this.jsonMediaType = new MediaType(mediaType.toString() + MT_JSON); this.setNegotiated(true); getVariants().add(new Variant(jsonMediaType)); getVariants().add(new Variant(MediaType.APPLICATION_JSON)); getVariants().add(new Variant(xmlMediaType)); getVariants().add(new Variant(MediaType.APPLICATION_XML)); getVariants().add(new Variant(MediaType.TEXT_XML)); getVariants().add(new Variant(MediaType.TEXT_PLAIN)); } protected BundleContext getBundleContext() { return (BundleContext) getContext().getAttributes().get( RestService.BUNDLE_CONTEXT_ATTR); } @SuppressWarnings("unchecked") protected ServiceTracker<RestApiExtension, Class<? extends ServerResource>> getTracker() { return (ServiceTracker<RestApiExtension, Class<? extends ServerResource>>) getContext() .getAttributes().get(RestService.TRACKER_ATTR); } protected FrameworkStartLevel getFrameworkStartLevel() { return getBundleContext().getBundle(0).adapt(FrameworkStartLevel.class); } protected Bundle getBundleFromKeys(final String bundleIdKey) { final String id = (String) getRequest().getAttributes() .get(bundleIdKey); if (id != null) { return getBundleContext().getBundle(Long.parseLong(id)); } return null; } protected Bundle[] getBundles() throws InvalidSyntaxException { final BundleContext context = getBundleContext(); if (getQuery().isEmpty()) { return context.getBundles(); } final Map<String, String> filterMap = getQuery().getValuesMap(); final Bundle[] bundles = context.getBundles(); final ArrayList<BundleRevision> workingSet = new ArrayList<BundleRevision>(); for (Bundle bundle : bundles) { workingSet.add(bundle.adapt(BundleRevision.class)); } for (final Map.Entry<String, String> filterDir : filterMap.entrySet()) { final String namespace; final Filter filter; if (filterDir.getValue() == null) { namespace = IdentityNamespace.IDENTITY_NAMESPACE; filter = FrameworkUtil.createFilter(filterDir.getKey()); } else { namespace = filterDir.getKey(); filter = FrameworkUtil.createFilter(filterDir.getValue()); } final Iterator<BundleRevision> iter = workingSet.iterator(); bundleLoop: while (iter.hasNext()) { final BundleRevision rev = iter.next(); final List<Capability> caps = rev .getCapabilities(namespace); for (final Capability cap : caps) { if (filter.matches(cap.getAttributes())) { continue bundleLoop; } } // no match, remove iter.remove(); } } if (workingSet.isEmpty()) { return new Bundle[0]; } final Bundle[] result = new Bundle[workingSet.size()]; int i = -1; for (final BundleRevision rev : workingSet) { result[++i] = rev.getBundle(); } return result; } protected Bundle[] getBundleVersionsBySymbolicName(String key) { final String symbolicName = (String) getRequest().getAttributes().get( key); if (symbolicName == null) { return null; } final ArrayList<Bundle> bundles = new ArrayList<Bundle>(); for (final Bundle bundle : getBundleContext().getBundles()) { if (bundle.getSymbolicName().equals(symbolicName)) { bundles.add(bundle); } } return bundles.toArray(new Bundle[bundles.size()]); } protected ServiceReference<?> getServiceReferenceFromKey(final String key) { final String id = (String) getRequest().getAttributes().get(key); if (id == null) { return null; } if (!id.matches(number)) { throw new IllegalArgumentException("Invalid service id " + id); } try { final ServiceReference<?>[] srefs = getBundleContext() .getServiceReferences((String) null, "(" + Constants.SERVICE_ID + "=" + id + ")"); return srefs != null ? srefs[0] : null; } catch (final InvalidSyntaxException e) { // does not happen e.printStackTrace(); return null; } } @SuppressWarnings("unchecked") protected Representation getRepresentation(final Object bean, final Variant variant) throws Exception { final Representation rep; final MediaType mt; if (xmlMediaType.includes(variant.getMediaType()) || MediaType.APPLICATION_XML.includes(variant.getMediaType()) || MediaType.TEXT_XML.includes(variant.getMediaType())) { mt = xmlMediaType; if (bean instanceof Map) { // special case: bundle header is a plain map and has no // reflector rep = toRepresentation(PojoReflector.mapToXml((Map<String, String>) bean), new Variant(MediaType.APPLICATION_ALL_XML)); } else { rep = toRepresentation(reflector.xmlFromBean((T) bean), new Variant(MediaType.APPLICATION_ALL_XML)); } } else if (jsonMediaType.includes(variant.getMediaType()) || MediaType.APPLICATION_JSON.includes(variant.getMediaType()) || MediaType.TEXT_PLAIN.includes(variant.getMediaType())) { mt = jsonMediaType; // in an ideal world we would not have to massage the data // in order to get it to serialize properly... if (bean instanceof Collection) { final Collection<Object> reprList = new ArrayList<Object>(); for (final Object o : (Collection<?>) bean) { if (o instanceof String) { reprList.add(o); } else { reprList.add(jsonObject(o)); } } final JSONArray arr = new JSONArray(reprList); rep = toRepresentation(arr, variant); } else if (bean instanceof Map) { rep = toRepresentation(new JSONObject((Map<?, ?>) bean), variant); } else { rep = toRepresentation(jsonObject(bean), variant); } } else { throw new UnsupportedOperationException(variant.getMediaType() .toString()); } rep.setMediaType(mt); return rep; } private JSONObject jsonObject(final Object bean) { if (bean instanceof ServicePojo) { // fix for buggy JSONObject final JSONObject json = new JSONObject(bean); try { json.put("properties", new JSONObject(((ServicePojo) bean).getProperties())); json.put("usingBundles", new JSONArray(((ServicePojo) bean).getUsingBundles())); } catch (final JSONException e) { e.printStackTrace(); } return json; } else { return new JSONObject(bean); } } protected T fromRepresentation(final Representation r, final MediaType mediaType) throws Exception { if (xmlMediaType.includes(mediaType) || MediaType.APPLICATION_XML.includes(mediaType) || MediaType.TEXT_XML.includes(mediaType)) { return reflector.beanFromXml(toObject(r, Document.class)); } else if (jsonMediaType.includes(mediaType) || MediaType.APPLICATION_JSON.includes(mediaType) || MediaType.TEXT_PLAIN.includes(mediaType)) { return reflector.beanFromJSONObject(toObject(r, JSONObject.class)); } throw new UnsupportedOperationException(mediaType .toString()); } protected Map<String, String> mapFromDict( final Dictionary<String, String> headers) { final HashMap<String, String> map = new HashMap<String, String>( headers.size()); final Enumeration<String> keyE = headers.keys(); while (keyE.hasMoreElements()) { final String key = keyE.nextElement(); map.put(key, headers.get(key)); } return map; } protected Representation ERROR(final Throwable t, final Variant variant) { t.printStackTrace(); if (t instanceof BundleException) { try { final Representation rep; final MediaType mt; if (MediaType.APPLICATION_ALL_XML.includes(variant.getMediaType())) { mt = MT_BE_XML; rep = getRepresentation(new BundleExceptionPojo( (BundleException) t), new Variant(MediaType.TEXT_XML)); } else { mt = MT_BE_JSON; rep = getRepresentation(new BundleExceptionPojo( (BundleException) t), new Variant(MediaType.APPLICATION_JSON)); } setStatus(Status.CLIENT_ERROR_BAD_REQUEST); rep.setMediaType(mt); return rep; } catch (final Exception ioe) { // fallback } } else if (t instanceof IllegalArgumentException) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST); return ERROR; } else if (t instanceof InvalidSyntaxException) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST); return ERROR; } setStatus(Status.SERVER_ERROR_INTERNAL, t); return ERROR; } protected Representation SUCCESS(final Status status) { setStatus(status); return SUCCESS; } protected Representation ERROR(final Status status) { setStatus(status); return ERROR; } protected Representation ERROR(final Status status, final Exception e) { setStatus(status, e); return ERROR; } protected Representation ERROR(final Status status, final String s) { setStatus(status, s); return ERROR; } }