e164abaf95c0ccd3adb07e3bb55d32b8f9f22ec8
[controller.git] / opendaylight / northbound / commons / src / main / java / org / opendaylight / controller / northbound / commons / NorthboundApplication.java
1 /**
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.northbound.commons;
10
11 import java.util.Dictionary;
12 import java.util.HashSet;
13 import java.util.List;
14 import java.util.Set;
15
16 import javax.ws.rs.core.Application;
17 import javax.ws.rs.ext.ContextResolver;
18 import javax.xml.bind.JAXBContext;
19 import javax.xml.bind.JAXBException;
20 import javax.xml.bind.annotation.XmlRootElement;
21
22 import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider;
23 import org.codehaus.jackson.map.DeserializationConfig;
24 import org.opendaylight.controller.northbound.bundlescanner.IBundleScanService;
25 import org.osgi.framework.Bundle;
26 import org.osgi.framework.BundleContext;
27 import org.osgi.framework.BundleReference;
28 import org.osgi.framework.FrameworkUtil;
29 import org.osgi.framework.ServiceException;
30 import org.osgi.framework.ServiceReference;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 /**
35  * Instance of javax.ws.rs.core.Application used to return the classes
36  * that will be instantiated for JAXRS processing. This hooks onto the
37  * bundle scanner service to provide JAXB classes to JAX-RS for prorcessing.
38  */
39 @SuppressWarnings("unchecked")
40 public class NorthboundApplication extends Application {
41     public static final String JAXRS_RESOURCES_MANIFEST_NAME = "Jaxrs-Resources";
42     public static final String JAXRS_EXCLUDES_MANIFEST_NAME = "Jaxrs-Exclude-Types";
43     private static final Logger LOGGER = LoggerFactory.getLogger(NorthboundApplication.class);
44     private final Set<Object> _singletons;
45
46     public NorthboundApplication() {
47         _singletons = new HashSet<Object>();
48         _singletons.add(new ContextResolver<JAXBContext>() {
49             JAXBContext jaxbContext;
50             @Override
51             public synchronized JAXBContext getContext(Class<?> type) {
52                 if (jaxbContext == null) {
53                     jaxbContext = newJAXBContext();
54                 }
55                 return jaxbContext;
56             }
57
58         } );
59         _singletons.add(getJsonProvider());
60         _singletons.add(new JacksonJsonProcessingExceptionMapper());
61     }
62
63     ////////////////////////////////////////////////////////////////
64     //  Application overrides
65     ////////////////////////////////////////////////////////////////
66
67     @Override
68     public Set<Object> getSingletons() {
69         return _singletons;
70     }
71
72     @Override
73     public Set<Class<?>> getClasses() {
74         Set<Class<?>> result = new HashSet<Class<?>>();
75         result.addAll(findJAXRSResourceClasses());
76         return result;
77     }
78
79     private static final JacksonJaxbJsonProvider getJsonProvider() {
80         JacksonJaxbJsonProvider jsonProvider = new JacksonJaxbJsonProvider();
81         jsonProvider.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,
82                 false);
83         return jsonProvider;
84     }
85
86     private BundleContext getBundleContext() {
87         ClassLoader tlcl = Thread.currentThread().getContextClassLoader();
88         Bundle bundle = null;
89
90         if (tlcl instanceof BundleReference) {
91             bundle = ((BundleReference) tlcl).getBundle();
92         } else {
93             LOGGER.warn("Unable to determine the bundle context based on " +
94                         "thread context classloader.");
95             bundle = FrameworkUtil.getBundle(this.getClass());
96         }
97         return (bundle == null ? null : bundle.getBundleContext());
98     }
99
100     private static final IBundleScanService lookupBundleScanner(BundleContext ctx) {
101         ServiceReference svcRef = ctx.getServiceReference(IBundleScanService.class);
102         if (svcRef == null) {
103             throw new ServiceException("Unable to lookup IBundleScanService");
104         }
105         return IBundleScanService.class.cast(ctx.getService(svcRef));
106     }
107
108     private final JAXBContext newJAXBContext() {
109         BundleContext ctx = getBundleContext();
110         IBundleScanService svc = lookupBundleScanner(ctx);
111         try {
112             List<Class<?>> cls = svc.getAnnotatedClasses(ctx,
113                     new String[] { XmlRootElement.class.getPackage().getName() },
114                     parseManifestEntry(ctx, JAXRS_EXCLUDES_MANIFEST_NAME),
115                     true);
116             return JAXBContext.newInstance(cls.toArray(new Class[cls.size()]));
117         } catch (JAXBException je) {
118             LOGGER.error("Error creating JAXBContext", je);
119             return null;
120         }
121     }
122
123     private final Set<Class<?>> findJAXRSResourceClasses() {
124         BundleContext ctx = getBundleContext();
125         String bundleName = ctx.getBundle().getSymbolicName();
126         Set<Class<?>> result = new HashSet<Class<?>>();
127         ServiceException recordException = null;
128         try {
129             IBundleScanService svc = lookupBundleScanner(ctx);
130             result.addAll(svc.getAnnotatedClasses(ctx,
131                     new String[] { javax.ws.rs.Path.class.getName() },
132                     null, false));
133         } catch (ServiceException se) {
134             recordException = se;
135             LOGGER.debug("Error finding JAXRS resource annotated classes in " +
136                     "bundle: {} error: {}.", bundleName, se.getMessage());
137             // the bundle scan service cannot be lookedup. Lets attempt to
138             // lookup the resources from the bundle manifest header
139             for (String c : parseManifestEntry(ctx, JAXRS_RESOURCES_MANIFEST_NAME)) {
140                 try {
141                     result.add(ctx.getBundle().loadClass(c));
142                 } catch (ClassNotFoundException cnfe) {
143                     LOGGER.error("Cannot load class: {} in bundle: {} " +
144                             "defined as MANIFEST JAX-RS resource", c, bundleName, cnfe);
145                 }
146             }
147         }
148
149         if (result.size() == 0) {
150             if (recordException != null) {
151                 throw recordException;
152             } else {
153                 throw new ServiceException("No resource classes found in bundle:" +
154                         ctx.getBundle().getSymbolicName());
155             }
156         }
157         return result;
158     }
159
160     private final Set<String> parseManifestEntry(BundleContext ctx, String name) {
161         Set<String> result = new HashSet<String>();
162         Dictionary<String,String> headers = ctx.getBundle().getHeaders();
163         String header = headers.get(name);
164         if (header != null) {
165             for (String s : header.split(",")) {
166                 s = s.trim();
167                 if (s.length() > 0) {
168                     result.add(s);
169                 }
170             }
171         }
172         return result;
173     }
174
175 }