/*
* 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.stanbol.commons.web.base.jersey;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.apache.felix.scr.annotations.References;
import org.apache.stanbol.commons.web.base.LinkResource;
import org.apache.stanbol.commons.web.base.NavigationLink;
import org.apache.stanbol.commons.web.base.ScriptResource;
import org.apache.stanbol.commons.web.base.WebFragment;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
/**
* Jersey-based RESTful endpoint for the Stanbol Enhancer engines and store.
* <p>
* This OSGi component serves as a bridge between the OSGi context and the Servlet context available to JAX-RS
* resources.
*/
@Component(immediate = true, metatype = true)
@References({
@Reference(name = "webFragment",
referenceInterface = WebFragment.class,
cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
policy = ReferencePolicy.DYNAMIC),
@Reference(name="component", referenceInterface=Object.class,
target="(javax.ws.rs=true)",
cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
policy=ReferencePolicy.DYNAMIC),
@Reference(name="navigationLink", referenceInterface=NavigationLink.class,
cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
policy=ReferencePolicy.DYNAMIC)})
public class JerseyEndpoint {
private final Logger log = LoggerFactory.getLogger(getClass());
@Property(value = "/")
public static final String ALIAS_PROPERTY = "org.apache.stanbol.commons.web.alias";
@Property(value = "/static")
public static final String STATIC_RESOURCES_URL_ROOT_PROPERTY = "org.apache.stanbol.commons.web.static.url";
@Reference
private EditableLayoutConfiguration layoutConfiguration;
/**
* The origins allowed for multi-host requests
*/
@Property(cardinality = 100, value = {"*"})
public static final String CORS_ORIGIN = "org.apache.stanbol.commons.web.cors.origin";
@Property(cardinality = 100, value = {"Location"})
public static final String CORS_ACCESS_CONTROL_EXPOSE_HEADERS = "org.apache.stanbol.commons.web.cors.access_control_expose_headers";
@Reference
HttpService httpService;
protected ComponentContext componentContext;
protected ServletContext servletContext;
protected final List<WebFragment> webFragments = new ArrayList<WebFragment>();
protected final List<String> registeredAliases = new ArrayList<String>();
protected Set<String> corsOrigins;
protected Set<String> exposedHeaders;
private Set<Object> components = new HashSet<Object>();
private List<NavigationLink> navigationLinks = new ArrayList<NavigationLink>();
public Dictionary<String,String> getInitParams() {
Dictionary<String,String> initParams = new Hashtable<String,String>();
// make jersey automatically turn resources into Viewable models and
// hence lookup matching freemarker templates
initParams.put("com.sun.jersey.config.feature.ImplicitViewables", "true");
return initParams;
}
@Activate
protected void activate(ComponentContext ctx) throws IOException,
ServletException,
NamespaceException,
ConfigurationException {
componentContext = ctx;
// init corsOrigins
Object values = componentContext.getProperties().get(CORS_ORIGIN);
if (values instanceof String && !((String) values).isEmpty()) {
corsOrigins = Collections.singleton((String) values);
} else if (values instanceof String[]) {
corsOrigins = new HashSet<String>(Arrays.asList((String[]) values));
} else if (values instanceof Iterable<?>) {
corsOrigins = new HashSet<String>();
for (Object value : (Iterable<?>) values) {
if (value != null && !value.toString().isEmpty()) {
corsOrigins.add(value.toString());
}
}
} else {
throw new ConfigurationException(CORS_ORIGIN,
"CORS origin(s) MUST be a String, String[], Iterable<String> (value:" + values + ")");
}
// parse headers to be exposed
values = componentContext.getProperties().get(CORS_ACCESS_CONTROL_EXPOSE_HEADERS);
if (values instanceof String && !((String) values).isEmpty()) {
exposedHeaders = Collections.singleton((String) values);
} else if (values instanceof String[]) {
exposedHeaders = new HashSet<String>(Arrays.asList((String[]) values));
} else if (values instanceof Iterable<?>) {
exposedHeaders = new HashSet<String>();
for (Object value : (Iterable<?>) values) {
if (value != null && !value.toString().isEmpty()) {
exposedHeaders.add(value.toString());
}
}
} else {
exposedHeaders = new HashSet<String>();
}
if (!webFragments.isEmpty()) {
initJersey();
}
}
/** Initialize the Jersey subsystem */
private synchronized void initJersey() throws NamespaceException, ServletException {
if (componentContext == null) {
//we have not yet been activated
return;
}
//end of STANBOL-1073 work around
if (componentContext == null) {
log.debug(" ... can not init Jersey Endpoint - Component not yet activated!");
//throw new IllegalStateException("Null ComponentContext, not activated?");
return;
}
shutdownJersey();
log.info("(Re)initializing the Stanbol Jersey subsystem");
// register all the JAX-RS resources into a a JAX-RS application and bind it to a configurable URL
// prefix
DefaultApplication app = new DefaultApplication();
String staticUrlRoot = (String) componentContext.getProperties().get(
STATIC_RESOURCES_URL_ROOT_PROPERTY);
String applicationAlias = (String) componentContext.getProperties().get(ALIAS_PROPERTY);
// incrementally contribute fragment resources
List<LinkResource> linkResources = new ArrayList<LinkResource>();
List<ScriptResource> scriptResources = new ArrayList<ScriptResource>();
for (WebFragment fragment : webFragments) {
log.debug("Registering web fragment '{}' into jaxrs application", fragment.getName());
linkResources.addAll(fragment.getLinkResources());
scriptResources.addAll(fragment.getScriptResources());
navigationLinks.removeAll(fragment.getNavigationLinks());
navigationLinks.addAll(fragment.getNavigationLinks());
app.contributeClasses(fragment.getJaxrsResourceClasses());
app.contributeSingletons(fragment.getJaxrsResourceSingletons());
}
app.contributeSingletons(components);
Collections.sort(linkResources);
Collections.sort(scriptResources);
Collections.sort(navigationLinks);
// bind the aggregate JAX-RS application to a dedicated servlet
ServletContainer container = new ServletContainer(
ResourceConfig.forApplication(app));
Bundle appBundle = componentContext.getBundleContext().getBundle();
httpService.registerServlet(applicationAlias, container, getInitParams(), null);
registeredAliases.add(applicationAlias);
// forward the main Stanbol OSGi runtime context so that JAX-RS resources can lookup arbitrary
// services
servletContext = container.getServletContext();
servletContext.setAttribute(BundleContext.class.getName(), componentContext.getBundleContext());
layoutConfiguration.setRootUrl(applicationAlias);
//servletContext.setAttribute(BaseStanbolResource.ROOT_URL, applicationAlias);
layoutConfiguration.setStaticResourcesRootUrl(staticUrlRoot);
//servletContext.setAttribute(BaseStanbolResource.STATIC_RESOURCES_ROOT_URL, staticUrlRoot);
layoutConfiguration.setLinkResources(linkResources);
//servletContext.setAttribute(BaseStanbolResource.LINK_RESOURCES, linkResources);
layoutConfiguration.setScriptResources(scriptResources);
//servletContext.setAttribute(BaseStanbolResource.SCRIPT_RESOURCES, scriptResources);
layoutConfiguration.setNavigationsLinks(navigationLinks);
//servletContext.setAttribute(BaseStanbolResource.NAVIGATION_LINKS, navigationLinks);
servletContext.setAttribute(CORS_ORIGIN, corsOrigins);
servletContext.setAttribute(CORS_ACCESS_CONTROL_EXPOSE_HEADERS, exposedHeaders);
log.info("JerseyEndpoint servlet registered at {}", applicationAlias);
}
/** Shutdown Jersey, if there's anything to do */
private synchronized void shutdownJersey() {
log.debug("Unregistering aliases {}", registeredAliases);
for (String alias : registeredAliases) {
httpService.unregister(alias);
}
registeredAliases.clear();
}
@Deactivate
protected void deactivate(ComponentContext ctx) {
shutdownJersey();
servletContext = null;
componentContext = null;
}
protected void bindWebFragment(WebFragment webFragment) throws IOException,
ServletException,
NamespaceException {
// TODO: support some ordering for jax-rs resource and template overrides?
webFragments.add(webFragment);
initJersey();
}
protected void unbindWebFragment(WebFragment webFragment) throws IOException,
ServletException,
NamespaceException {
navigationLinks.removeAll(webFragment.getNavigationLinks());
webFragments.remove(webFragment);
initJersey();
}
protected void bindComponent(Object component) throws IOException,
ServletException,
NamespaceException {
components.add(component);
initJersey();
}
protected void unbindComponent(Object component) throws IOException,
ServletException,
NamespaceException {
components.remove(component);
initJersey();
}
protected void bindNavigationLink(NavigationLink navigationLink) {
navigationLinks.add(navigationLink);
}
protected void unbindNavigationLink(NavigationLink navigationLink) {
navigationLinks.remove(navigationLink);
}
public List<WebFragment> getWebFragments() {
return webFragments;
}
}