/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.kernel.publisher;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.common.site.SiteURL;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
import org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet;
import org.json.JSONException;
import org.osgi.framework.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* This custom implementation of the <code>CXFNonSpringJaxrsServlet</code> was
* done following the <a href=
* "http://cxf.apache.org/docs/jax-rs.html#JAX-RS-WithCXFNonSpringJaxrsServlet"
* >cxf documentation</a>.
*/
public class JAXRSServlet extends CXFNonSpringJaxrsServlet {
/** The logging facility */
private static final Logger logger = LoggerFactory.getLogger(JAXRSServlet.class);
/** The serial version uid */
private static final long serialVersionUID = 7336130764437993613L;
/** The servlet address */
private String address = null;
/** The wrapped endpoint */
private Object service = null;
/** The bundle that defined this servlet */
private Bundle bundle = null;
/** The associated site (if not a global endpoint) */
private Site site = null;
/** The environment */
private Environment environment = null;
/**
* Creates a new servlet that maps <code>service</code> to the given address.
*
* @param address
* the servlet address
* @param service
* the service implementation
* @param bundle
* the bundle that defined this service
*/
JAXRSServlet(String address, Object service, Bundle bundle) {
if (address == null)
throw new IllegalArgumentException("Address can't be null");
if (service == null)
throw new IllegalArgumentException("Service implementation can't be null");
if (bundle == null)
throw new IllegalArgumentException("Defining bundle can't be null");
this.address = address;
this.service = service;
this.bundle = bundle;
}
@Override
protected void handleRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException {
// If a site registered this endpoint, other sites should not be able to
// access it
if (site != null) {
String requestPath = request.getRequestURL().toString();
SiteURL siteUrl = null;
logger.trace("Making sure the endpoint should be accessible through site '{}'", site);
for (SiteURL url : site.getHostnames()) {
if (environment != null && !environment.equals(url.getEnvironment()))
continue;
if (requestPath.startsWith(url.toExternalForm())) {
siteUrl = url;
break;
}
}
if (siteUrl == null || !site.isOnline()) {
try {
logger.debug("Request to site '{}' cannot acces {}", this);
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
} catch (IOException e) {
logger.debug("Error sending response back to client: {}", e.getMessage());
}
}
logger.trace("Access through site '{}' to {} granted", site, request.getRequestURI());
}
if (requiresJson(request)) {
JSONResponseWrapper wrappedResponse = new JSONResponseWrapper(response);
super.handleRequest(request, wrappedResponse);
try {
wrappedResponse.finishResponse();
} catch (IOException e) {
logger.error("Writing json to response failed: " + e.getMessage());
throw new ServletException(e);
} catch (JSONException e) {
logger.error("Conversion to json failed: " + e.getMessage());
throw new ServletException(e);
}
} else {
super.handleRequest(request, response);
}
}
/**
* Returns <code>true</code> if the request is asking for a <code>json</code>
* response.
*
* @param request
* the request
* @return <code>true</code> if a json response is requested
*/
private boolean requiresJson(HttpServletRequest request) {
String uri = request.getRequestURI();
String parameter = request.getParameter("json");
String accepts = request.getHeader("Accept");
if (uri.endsWith(".json") || uri.endsWith("/json"))
return true;
if (parameter != null)
return true;
if (accepts != null && ("application/json".equals(accepts) || "text/json".equals(accepts)))
return true;
return false;
}
/**
* Returns the service that is being wrapped by this servlet.
*
* @return the service implementation
*/
public Object getService() {
return service;
}
/**
* {@inheritDoc}
*
* @see org.apache.cxf.transport.servlet.AbstractHTTPServlet#service(javax.servlet.ServletRequest,
* javax.servlet.ServletResponse)
*/
@Override
public void service(HttpServletRequest request, HttpServletResponse res)
throws ServletException, IOException {
}
/**
* {@inheritDoc}
*
* @see org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet#loadBus(javax.servlet.ServletConfig)
*/
@Override
public void loadBus(ServletConfig servletConfig) throws ServletException {
super.loadBus(servletConfig);
ClassLoader bundleClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader delegateClassLoader = JAXRSServerFactoryBean.class.getClassLoader();
try {
// The JAXRSServerFactoryBean needs access to resources on it's own
// bundle classpath (META-INF/cxf.xml, META-INF/cxf/osgi.xml). Therefore
// we need to adjust the context class loader
Thread.currentThread().setContextClassLoader(delegateClassLoader);
// Create the endpoint
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
sf.setAddress(address);
sf.setResourceClasses(service.getClass());
sf.setResourceProvider(service.getClass(), new SingletonResourceProvider(service));
sf.create();
} finally {
Thread.currentThread().setContextClassLoader(bundleClassLoader);
}
}
/**
* {@inheritDoc}
*
* Note that this method is intentionally <i>not</i> calling its super
* implementation, since this will throw an exception because no service
* classes have been registered previously.
*
* @see org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet#getServiceClasses(javax.servlet.ServletConfig,
* boolean)
*/
@SuppressWarnings("rawtypes")
@Override
protected List<Class> getServiceClasses(ServletConfig servletConfig,
boolean modelAvailable) throws ServletException {
List<Class> classes = new ArrayList<Class>();
classes.add(service.getClass());
return classes;
}
/**
* {@inheritDoc}
*
* @see org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet#getResourceProviders(javax.servlet.ServletConfig,
* java.util.List)
*/
@SuppressWarnings("rawtypes")
@Override
protected Map<Class, ResourceProvider> getResourceProviders(
ServletConfig servletConfig, List<Class> resourceClasses)
throws ServletException {
Map<Class, ResourceProvider> providers = super.getResourceProviders(servletConfig, resourceClasses);
providers.put(service.getClass(), new SingletonResourceProvider(service));
return providers;
}
/**
* Returns the bundle that defined this endpoint.
*
* @return the bundle
*/
Bundle getBundle() {
return bundle;
}
/**
* Sets the site that is associated with this REST endpoint.
*
* @param site
* the site
*/
void setSite(Site site) {
this.site = site;
}
/**
* Sets the current environment.
*
* @param environment
* the environment
*/
void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return service.toString();
}
}