001: /*
002: * Copyright 2005-2007 Noelios Consulting.
003: *
004: * The contents of this file are subject to the terms of the Common Development
005: * and Distribution License (the "License"). You may not use this file except in
006: * compliance with the License.
007: *
008: * You can obtain a copy of the license at
009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
010: * language governing permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL HEADER in each file and
013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
014: * applicable, add the following below this CDDL HEADER, with the fields
015: * enclosed by brackets "[]" replaced with your own identifying information:
016: * Portions Copyright [yyyy] [name of copyright owner]
017: */
018:
019: package com.noelios.restlet.ext.servlet;
020:
021: import java.io.IOException;
022: import java.util.List;
023:
024: import java.lang.reflect.InvocationTargetException;
025:
026: import javax.servlet.ServletException;
027: import javax.servlet.http.HttpServlet;
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.restlet.Application;
032: import org.restlet.Component;
033: import org.restlet.Context;
034: import org.restlet.Server;
035: import org.restlet.data.Protocol;
036:
037: import com.noelios.restlet.http.HttpServerHelper;
038:
039: /**
040: * Servlet acting like an HTTP server connector.
041: * See <a href="/documentation/1.0/faq#02">Developper FAQ
042: * #2</a> for details on how to integrate a Restlet application into a servlet
043: * container.<br/> Here is a sample configuration for your Restlet webapp:
044: *
045: * <pre>
046: * <?xml version="1.0" encoding="ISO-8859-1"?>
047: * <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
048: * <web-app>
049: * <display-name>Restlet adapter</display-name>
050: *
051: * <!-- Your application class name -->
052: * <context-param>
053: * <param-name>org.restlet.application</param-name>
054: * <param-value>com.noelios.restlet.test.TraceApplication</param-value>
055: * </context-param>
056: *
057: * <!-- Restlet adapter -->
058: * <servlet>
059: * <servlet-name>ServerServlet</servlet-name>
060: * <servlet-class>com.noelios.restlet.ext.servlet.ServerServlet</servlet-class>
061: * </servlet>
062: *
063: * <!-- Catch all requests -->
064: * <servlet-mapping>
065: * <servlet-name>ServerServlet</servlet-name>
066: * <url-pattern>/*</url-pattern>
067: * </servlet-mapping>
068: * </web-app>
069: * </pre>
070: *
071: * The enumeration of initParameters of your Servlet will be copied to the
072: * "context.parameters" property of your application. This way, you can pass
073: * additional initialization parameters to your Restlet application, and share
074: * them with existing Servlets.
075: *
076: * @see <a href="http://java.sun.com/j2ee/">J2EE home page</a>
077: * @author Jerome Louvel (contact@noelios.com)
078: */
079: public class ServerServlet extends HttpServlet {
080: /**
081: * The Servlet context initialization parameter's name containing the name
082: * of the Servlet context attribute that should be used to store the Restlet
083: * Application instance.
084: */
085: private static final String NAME_APPLICATION_ATTRIBUTE = "org.restlet.attribute.application";
086:
087: /** The default value for the NAME_APPLICATION_ATTRIBUTE parameter. */
088: private static final String NAME_APPLICATION_ATTRIBUTE_DEFAULT = "com.noelios.restlet.ext.servlet.ServerServlet.application";
089:
090: /**
091: * The Servlet context initialization parameter's name containing the name
092: * of the Servlet context attribute that should be used to store the Restlet
093: * Component instance.
094: */
095: private static final String NAME_COMPONENT_ATTRIBUTE = "org.restlet.attribute.component";
096:
097: /** The default value for the NAME_COMPONENT_ATTRIBUTE parameter. */
098: private static final String NAME_COMPONENT_ATTRIBUTE_DEFAULT = "com.noelios.restlet.ext.servlet.ServerServlet.component";
099:
100: /**
101: * The Servlet context initialization parameter's name containing the name
102: * of the Servlet context attribute that should be used to store the HTTP
103: * server connector instance.
104: */
105: private static final String NAME_SERVER_ATTRIBUTE = "org.restlet.attribute.server";
106:
107: /** The default value for the NAME_SERVER_ATTRIBUTE parameter. */
108: private static final String NAME_SERVER_ATTRIBUTE_DEFAULT = "com.noelios.restlet.ext.servlet.ServerServlet.server";
109:
110: /** Serial version identifier. */
111: private static final long serialVersionUID = 1L;
112:
113: /** The associated Restlet application. */
114: private transient Application application;
115:
116: /** The associated Restlet component. */
117: private transient Component component;
118:
119: /** The associated HTTP server helper. */
120: private transient HttpServerHelper helper;
121:
122: /**
123: * Constructor.
124: */
125: public ServerServlet() {
126: this .application = null;
127: this .component = null;
128: this .helper = null;
129: }
130:
131: /**
132: * Creates the single Application used by this Servlet.
133: *
134: * @param context
135: * The Context for the Application
136: *
137: * @return The newly created Application or null if unable to create
138: */
139: public Application createApplication(Context context) {
140: Application application = null;
141: // Try to instantiate a new target application
142: // First, find the application class name
143: String applicationClassName = getInitParameter(Application.KEY,
144: null);
145:
146: // Load the application class using the given class name
147: if (applicationClassName != null) {
148: try {
149: // According to
150: // http://www.caucho.com/resin-3.0/webapp/faq.xtp#Class.forName()-doesn't-seem-to-work-right
151: // this approach may need to used when loading classes.
152: Class<?> targetClass;
153: ClassLoader loader = Thread.currentThread()
154: .getContextClassLoader();
155:
156: if (loader != null)
157: targetClass = Class.forName(applicationClassName,
158: false, loader);
159: else
160: targetClass = Class.forName(applicationClassName);
161:
162: try {
163: // Create a new instance of the application class by
164: // invoking the constructor with the Context parameter.
165: application = (Application) targetClass
166: .getConstructor(Context.class).newInstance(
167: context);
168: } catch (NoSuchMethodException e) {
169: log(
170: "[Noelios Restlet Engine] - The ServerServlet couldn't invoke the constructor of the target class. Please check this class has a constructor with a single parameter of Context. The empty constructor and the context setter wille used instead. "
171: + applicationClassName, e);
172: // The constructor with the Context parameter does not
173: // exist. Instantiate an application with the default
174: // constructor then invoke the setContext method.
175: application = (Application) targetClass
176: .getConstructor().newInstance();
177: }
178: } catch (ClassNotFoundException e) {
179: log(
180: "[Noelios Restlet Engine] - The ServerServlet couldn't find the target class. Please check that your classpath includes "
181: + applicationClassName, e);
182:
183: } catch (InstantiationException e) {
184: log(
185: "[Noelios Restlet Engine] - The ServerServlet couldn't instantiate the target class. Please check this class has an empty constructor "
186: + applicationClassName, e);
187: } catch (IllegalAccessException e) {
188: log(
189: "[Noelios Restlet Engine] - The ServerServlet couldn't instantiate the target class. Please check that you have to proper access rights to "
190: + applicationClassName, e);
191: } catch (NoSuchMethodException e) {
192: log(
193: "[Noelios Restlet Engine] - The ServerServlet couldn't invoke the constructor of the target class. Please check this class has a constructor with a single parameter of Context "
194: + applicationClassName, e);
195: } catch (InvocationTargetException e) {
196: log(
197: "[Noelios Restlet Engine] - The ServerServlet couldn't instantiate the target class. An exception was thrown while creating "
198: + applicationClassName, e);
199: }
200:
201: if (application != null) {
202: // Set the context based on the Servlet's context
203: application.setContext(new ServletContextAdapter(this ,
204: application, context));
205: }
206: }
207:
208: return application;
209: }
210:
211: /**
212: * Creates the associated HTTP server handling calls.
213: *
214: * @param request
215: * The HTTP Servlet request.
216: * @return The new HTTP server handling calls.
217: */
218: public HttpServerHelper createServer(HttpServletRequest request) {
219: HttpServerHelper result = null;
220: Component component = getComponent();
221: Application application = getApplication();
222:
223: if ((component != null) && (application != null)) {
224: // First, let's locate the closest component
225: Server server = new Server(component.getContext(),
226: (List<Protocol>) null, request.getLocalAddr(),
227: request.getLocalPort(), component);
228: result = new HttpServerHelper(server);
229:
230: // Attach the application
231: String uriPattern = request.getContextPath()
232: + request.getServletPath();
233: component.getDefaultHost().attach(uriPattern, application);
234: }
235:
236: return result;
237: }
238:
239: @Override
240: public void destroy() {
241: if ((getApplication() != null)
242: && (getApplication().isStarted())) {
243: try {
244: getApplication().stop();
245: } catch (Exception e) {
246: log(
247: "Error during the stopping of the Restlet Application",
248: e);
249: }
250: }
251:
252: super .destroy();
253: }
254:
255: /**
256: * Returns the application. It creates a new one if none exists.
257: *
258: * @return The application.
259: */
260: public Application getApplication() {
261: Application result = this .application;
262:
263: if (result == null) {
264: synchronized (ServerServlet.class) {
265: // Find the attribute name to use to store the application
266: String applicationAttributeName = getInitParameter(
267: NAME_APPLICATION_ATTRIBUTE,
268: NAME_APPLICATION_ATTRIBUTE_DEFAULT);
269:
270: // Look up the attribute for a target
271: result = (Application) getServletContext()
272: .getAttribute(applicationAttributeName);
273:
274: if (result == null) {
275: result = createApplication(getComponent()
276: .getContext());
277: getServletContext().setAttribute(
278: applicationAttributeName, result);
279: }
280:
281: this .application = result;
282: }
283: }
284:
285: return result;
286: }
287:
288: /**
289: * Returns the component. It creates a new one if none exists.
290: *
291: * @return The component.
292: */
293: public Component getComponent() {
294: Component result = this .component;
295:
296: if (result == null) {
297: synchronized (ServerServlet.class) {
298: // Find the attribute name to use to store the component
299: String componentAttributeName = getInitParameter(
300: NAME_COMPONENT_ATTRIBUTE,
301: NAME_COMPONENT_ATTRIBUTE_DEFAULT);
302:
303: // Look up the attribute for a target
304: result = (Component) getServletContext().getAttribute(
305: componentAttributeName);
306:
307: if (result == null) {
308: result = new Component();
309: getServletContext().setAttribute(
310: componentAttributeName, result);
311: }
312:
313: this .component = result;
314: }
315: }
316:
317: return result;
318: }
319:
320: /**
321: * Returns the value of a given initialization parameter, first from the
322: * Servlet configuration, then from the Web Application context.
323: *
324: * @param name
325: * The parameter name.
326: * @param defaultValue
327: * The default to use in case the parameter is not found.
328: * @return The value of the parameter or null.
329: */
330: public String getInitParameter(String name, String defaultValue) {
331: String result = getServletConfig().getInitParameter(name);
332:
333: if (result == null) {
334: result = getServletConfig().getServletContext()
335: .getInitParameter(name);
336: }
337:
338: if (result == null) {
339: result = defaultValue;
340: }
341:
342: return result;
343: }
344:
345: /**
346: * Returns the associated HTTP server handling calls. It creates a new one
347: * if none exists.
348: *
349: * @param request
350: * The HTTP Servlet request.
351: * @return The HTTP server handling calls.
352: */
353: public HttpServerHelper getServer(HttpServletRequest request) {
354: HttpServerHelper result = this .helper;
355:
356: if (result == null) {
357: synchronized (ServerServlet.class) {
358: // Find the attribute name to use to store the server reference
359: String serverAttributeName = getInitParameter(
360: NAME_SERVER_ATTRIBUTE,
361: NAME_SERVER_ATTRIBUTE_DEFAULT);
362:
363: // Look up the attribute for a target
364: result = (HttpServerHelper) getServletContext()
365: .getAttribute(serverAttributeName);
366:
367: if (result == null) {
368: result = createServer(request);
369: getServletContext().setAttribute(
370: serverAttributeName, result);
371: }
372:
373: this .helper = result;
374: }
375: }
376:
377: return result;
378: }
379:
380: @Override
381: public void init() throws ServletException {
382: if ((getApplication() != null)
383: && (getApplication().isStopped())) {
384: try {
385: getApplication().start();
386: } catch (Exception e) {
387: log(
388: "Error during the starting of the Restlet Application",
389: e);
390: }
391: }
392: }
393:
394: /**
395: * Services a HTTP Servlet request as an uniform call.
396: *
397: * @param request
398: * The HTTP Servlet request.
399: * @param response
400: * The HTTP Servlet response.
401: */
402: public void service(HttpServletRequest request,
403: HttpServletResponse response) throws ServletException,
404: IOException {
405: HttpServerHelper helper = getServer(request);
406:
407: if (helper != null) {
408: helper.handle(new ServletCall(helper.getServer(), request,
409: response));
410: } else {
411: log("[Noelios Restlet Engine] - Unable to get the Restlet HTTP server connector. Status code 500 returned.");
412: response.sendError(500);
413: }
414: }
415:
416: }
|