001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: Gate.java 3784 2007-06-11 16:44:35Z gbevin $
007: */
008: package com.uwyn.rife.engine;
009:
010: import com.uwyn.rife.Version;
011: import com.uwyn.rife.config.Config;
012: import com.uwyn.rife.config.RifeConfig;
013: import com.uwyn.rife.engine.exceptions.DeferException;
014: import com.uwyn.rife.engine.exceptions.ElementCompilationFailedException;
015: import com.uwyn.rife.engine.exceptions.EngineException;
016: import com.uwyn.rife.rep.Rep;
017: import com.uwyn.rife.rep.participants.ParticipantSite;
018: import com.uwyn.rife.resources.ResourceFinderClasspath;
019: import com.uwyn.rife.template.Template;
020: import com.uwyn.rife.template.TemplateFactory;
021: import com.uwyn.rife.template.exceptions.SyntaxErrorException;
022: import com.uwyn.rife.tools.ExceptionFormattingUtils;
023: import com.uwyn.rife.tools.ExceptionUtils;
024: import com.uwyn.rife.tools.StringUtils;
025: import com.uwyn.rife.tools.TerracottaUtils;
026: import java.io.File;
027: import java.io.IOException;
028: import java.util.Set;
029: import java.util.logging.Logger;
030:
031: public class Gate {
032: public final static String INIT_PARAM_SITE_XML_PATH = "site.xml.path";
033: public final static String REQUEST_ATTRIBUTE_RIFE_ENGINE_EXCEPTION = "rife.engine.exception";
034:
035: private Throwable mInitException = null;
036: private InitConfig mInitConfig = null;
037: private Site mSiteFromInit = null;
038: private boolean mSiteFromRep = false;
039: private volatile Boolean mTempPathSet = false;
040: private String mWebappContextPath = null;
041:
042: public Gate() {
043: mWebappContextPath = RifeConfig.Engine.getWebappContextPath();
044: }
045:
046: public Gate(Site site) {
047: this ();
048:
049: mSiteFromInit = site;
050: }
051:
052: public void init(InitConfig config) {
053: mInitConfig = config;
054:
055: setApplicationClasspath(config);
056:
057: if (TerracottaUtils.isTcPresent()) {
058: // force early creating of the TemplateClassLoader - needed for DSO page-in on second node
059: TemplateFactory.HTML
060: .get("errors.rife.engine_error_default");
061: }
062: }
063:
064: private void setApplicationClasspath(InitConfig config) {
065: // initialize the classpath of the web application
066: // this is the best possible effort that can be made
067: // since there's no standard way to obtain the classpath
068: // of a web application
069: StringBuilder application_classpath = new StringBuilder();
070: if (mInitConfig.getServletContext() != null) {
071: // get the root of the web application
072: String webapp_root = mInitConfig.getServletContext()
073: .getRealPath("/");
074: if (webapp_root != null) {
075: if (webapp_root.endsWith(File.separator)) {
076: webapp_root = webapp_root.substring(0, webapp_root
077: .length()
078: - File.separator.length());
079: }
080:
081: // add the classes dir
082: application_classpath.append(webapp_root);
083: application_classpath.append(File.separator).append(
084: "WEB-INF").append(File.separator).append(
085: "classes");
086:
087: // add the jars in the lib dir
088: Set<String> lib_resources = mInitConfig
089: .getServletContext().getResourcePaths(
090: "/WEB-INF/lib");
091: if (lib_resources != null) {
092: for (String lib_resource : lib_resources) {
093: if (lib_resource.endsWith(".jar")) {
094: application_classpath
095: .append(File.pathSeparator);
096: application_classpath.append(webapp_root);
097: application_classpath.append(lib_resource);
098: }
099: }
100: }
101: } else {
102: Logger
103: .getLogger("com.uwyn.rife.engine")
104: .warning(
105: "Due to the behavior of your servlet container ("
106: + mInitConfig
107: .getServletContext()
108: .getServerInfo()
109: + "), automatic element compilation is not supported and might fail while resolving imported classes.");
110: }
111: }
112:
113: // add the rife.webapp.path paths
114: if (EngineClassLoaderRifeWebappPath.RIFE_WEBAPP_PATH != null) {
115: for (String path : EngineClassLoaderRifeWebappPath.RIFE_WEBAPP_PATH) {
116: application_classpath.append(File.pathSeparator);
117: application_classpath.append(path);
118: }
119: }
120:
121: if (application_classpath.length() > 0) {
122: RifeConfig.Global
123: .setApplicationClassPath(application_classpath
124: .toString());
125: }
126: }
127:
128: public boolean handleRequest(String gateUrl, String elementUrl,
129: Request request, Response response) {
130: ensureExistingTempPath(request);
131:
132: // check if the gateUrl hasn't been overridden by a webapp context path configuration parameter
133: if (mWebappContextPath != null) {
134: gateUrl = mWebappContextPath;
135: }
136:
137: // ensure a valid element url
138: if (null == elementUrl || 0 == elementUrl.length()) {
139: elementUrl = "/";
140: }
141:
142: // strip away the optional path parameters
143: int path_parameters_index = elementUrl.indexOf(";");
144: if (path_parameters_index != -1) {
145: elementUrl = elementUrl.substring(0, path_parameters_index);
146: }
147:
148: // If an internal error occured, try to build the site at each request
149: Site site = getSite();
150:
151: // Handle the request
152: // check if an exception occurred during the initialization
153: if (mInitException != null) {
154: printExceptionDetails(response, mInitException);
155: return true;
156: }
157:
158: // if no site is ready yet, don't continue processing
159: if (null == site) {
160: return handleSiteNotReady(elementUrl, response);
161: }
162:
163: // Set up the element request and process it.
164: try {
165: ElementToService element_match = site
166: .findElementForRequest(elementUrl);
167:
168: // If no element was found, don't continue executing the gate logic.
169: // This could allow a next filter in the chain to be executed.
170: if (null == element_match) {
171: return false;
172: }
173:
174: ElementInfo element_info = element_match.getElementInfo();
175: StateStore state_store = element_info.getStateStore();
176:
177: state_store.init(request);
178: request.init(state_store);
179:
180: RequestState request_state = RequestState.getInstance(
181: mInitConfig, site, request, response, gateUrl,
182: state_store.restoreResultStates(request),
183: element_match.getPathInfo(), element_info);
184: request_state.service();
185: response.close();
186: } catch (DeferException e) {
187: return false;
188: } catch (Throwable e) {
189: handleRequestException(site, request, response, e);
190: }
191:
192: return true;
193: }
194:
195: private void ensureExistingTempPath(Request request)
196: throws EngineException {
197: if (!mTempPathSet
198: && (!Config.hasRepInstance() || !Config
199: .getRepInstance().hasParameter(
200: RifeConfig.Global.PARAM_TEMP_PATH))) {
201: // construct a temp path which is unique for the server and virtual host
202: String tmpdir = System.getProperty("java.io.tmpdir");
203: tmpdir = StringUtils.stripFromEnd(tmpdir, File.separator);
204: String context_path = request.getContextPath();
205: context_path = context_path
206: .replace('/', File.separatorChar);
207: RifeConfig.Global.setTempPath(tmpdir + File.separator
208: + "rife_" + request.getServerName() + "_"
209: + request.getServerPort() + "_" + context_path);
210:
211: // ensure that the temp path exists
212: File file_temp_path = new File(RifeConfig.Global
213: .getTempPath());
214: if (!file_temp_path.exists()) {
215: if (!file_temp_path.mkdirs()) {
216: throw new EngineException(
217: "Couldn't create the temporary directory : '"
218: + RifeConfig.Global.getTempPath()
219: + "'.");
220: }
221: } else if (!file_temp_path.isDirectory()) {
222: throw new EngineException(
223: "The element package directory '"
224: + RifeConfig.Global.getTempPath()
225: + "' exists but is not a directory.");
226: } else if (!file_temp_path.canWrite()) {
227: throw new EngineException(
228: "The element package directory '"
229: + RifeConfig.Global.getTempPath()
230: + "' is not writable.");
231: }
232:
233: // set the flag that indicates that the temp path has been set
234: synchronized (mTempPathSet) {
235: mTempPathSet = true;
236: }
237: } else {
238: synchronized (mTempPathSet) {
239: mTempPathSet = true;
240: }
241: }
242: }
243:
244: public Site getSite() {
245: // if the site has previously been initialized from init, return the
246: // result immediately
247: if (mSiteFromInit != null) {
248: return mSiteFromInit;
249: }
250:
251: Site result = null;
252:
253: try {
254: // Ensure the presence of a servlet init configuration
255: if (null == mInitConfig) {
256: throw new Exception(
257: "No servlet configuration is available, it is required to set up the site structure.");
258: }
259:
260: // Clear the init exception
261: mInitException = null;
262:
263: // If there's a site participant, try to obtain a site instance from it
264: if (Site.hasRepInstance()) {
265: // only continue if the default repository has finished initializing
266: if (!Site.getRepParticipant().isFinished()
267: && !RifeConfig.Engine.getResponseRequiresSite()) {
268: return null;
269: }
270:
271: result = Site.getRepInstance();
272:
273: if (null == result
274: && Rep
275: .getParticipant(Site.DEFAULT_PARTICIPANT_NAME) instanceof ParticipantSite
276: && ((ParticipantSite) Rep
277: .getParticipant(Site.DEFAULT_PARTICIPANT_NAME))
278: .getException() != null) {
279: throw ((ParticipantSite) Rep
280: .getParticipant(Site.DEFAULT_PARTICIPANT_NAME))
281: .getException();
282: }
283: }
284:
285: // only proces the init parameter if the site hasn't previously been
286: // obtained frop the rep
287: if (!mSiteFromRep) {
288: // Try to obtain a site xml path specification from an init config
289: String site_xml_path = mInitConfig
290: .getInitParameter(INIT_PARAM_SITE_XML_PATH);
291: if (null == site_xml_path) {
292: // If it doesn't exist, launch an error when the site hasn't been
293: // obtained through a participant earlier on.
294: if (null == result) {
295: throw new Exception(
296: "A site couldn't be obtained through the repository from the "
297: + Site.DEFAULT_PARTICIPANT_NAME
298: + " participant, nor through the init parameter '"
299: + INIT_PARAM_SITE_XML_PATH
300: + "'.");
301: }
302: // since no init param exists and a site could be obtained
303: // from the rep, remember this setup
304: else {
305: mSiteFromRep = true;
306: }
307: } else {
308: // If the site xml path could be found, use it to populate a new
309: // site instance. This will override a prior site instance that was
310: // obtained through a participant.
311: // Like that, a site xml path specified in an init config overrides
312: // a site participant.
313: ResourceFinderClasspath resourcefinder = ResourceFinderClasspath
314: .getInstance();
315: SiteBuilder builder = new SiteBuilder(
316: site_xml_path, resourcefinder);
317:
318: result = builder.getSite();
319: mSiteFromInit = result;
320: mSiteFromRep = false;
321: }
322: }
323: } catch (Throwable e) {
324: handleSiteInitException(e);
325: }
326:
327: return result;
328: }
329:
330: private void handleSiteInitException(Throwable e) {
331: // ensure the later init exceptions don't overwrite earlier ones
332: if (null == mInitException) {
333: if (RifeConfig.Engine.getPrettyEngineExceptions()) {
334: mInitException = e;
335: } else {
336: if (e instanceof RuntimeException) {
337: throw (RuntimeException) e;
338: } else {
339: throw new RuntimeException(e);
340: }
341: }
342: }
343: }
344:
345: private boolean handleSiteNotReady(String elementUrl,
346: Response response) {
347: // check if the url matches one of the passthrough suffixes
348: boolean passthrough = false;
349: for (String suffix : RifeConfig.Engine
350: .getSiteInitializingPassthroughSuffixes()) {
351: if (elementUrl.endsWith(suffix)) {
352: passthrough = true;
353: break;
354: }
355: }
356:
357: if (passthrough) {
358: return false;
359: } else {
360: String initializing_url = RifeConfig.Engine
361: .getSiteInitializingRedirectUrl();
362: try {
363: // either set the 'service is unavailable' status
364: if (null == initializing_url) {
365: response.sendError(503); // SC_SERVICE_UNAVAILABLE
366: }
367: // either redirect to a dedicated URL
368: else {
369: response.sendRedirect(initializing_url);
370: }
371: } catch (EngineException e) {
372: response.sendError(503); // SC_SERVICE_UNAVAILABLE
373: }
374: return true;
375: }
376: }
377:
378: private void handleRequestException(Site site, Request request,
379: Response response, Throwable e) {
380: if (site != null) {
381: site.resetLastModificationCheck();
382: }
383:
384: String message = "Error on host " + request.getServerName()
385: + ":" + request.getServerPort() + "/"
386: + request.getContextPath();
387: if (RifeConfig.Engine.getLogEngineExceptions()) {
388: Logger.getLogger("com.uwyn.rife.engine").severe(
389: message + "\n"
390: + ExceptionUtils.getExceptionStackTrace(e));
391: }
392:
393: if (!RifeConfig.Engine.getPrettyEngineExceptions()) {
394: request.setAttribute(
395: REQUEST_ATTRIBUTE_RIFE_ENGINE_EXCEPTION, e);
396:
397: if (e instanceof RuntimeException) {
398: throw (RuntimeException) e;
399: } else {
400: throw new RuntimeException(message, e);
401: }
402: }
403:
404: printExceptionDetails(response, e);
405: }
406:
407: private void printExceptionDetails(Response response,
408: Throwable exception) {
409: TemplateFactory template_factory = null;
410: if (response.isContentTypeSet()) {
411: String content_type = response.getContentType();
412: if (content_type.startsWith("text/xml")
413: || content_type.startsWith("application/xhtml+xml")) {
414: template_factory = TemplateFactory.XML;
415: response.setContentType("text/xml");
416: }
417: }
418: if (null == template_factory) {
419: template_factory = TemplateFactory.HTML;
420: response.setContentType("text/html");
421: }
422:
423: // pretty exception formatting and outputting instead of the default servlet
424: // engine's formatting
425: Template template = null;
426:
427: Throwable cause = exception;
428: while (cause != null && cause.getCause() != cause) {
429:
430: if (cause instanceof ElementCompilationFailedException
431: || cause instanceof SyntaxErrorException) {
432: template = template_factory
433: .get("errors.rife.engine_error_compilation");
434: break;
435: }
436:
437: cause = cause.getCause();
438: }
439:
440: if (null == template) {
441: template = template_factory
442: .get("errors.rife.engine_error_default");
443: }
444:
445: template.setValue("exceptions", ExceptionFormattingUtils
446: .formatExceptionStackTrace(exception, template));
447: template.setValue("RIFE_VERSION", template.getEncoder().encode(
448: Version.getVersion()));
449:
450: try {
451: response.getWriter().print(template.getContent());
452: } catch (IOException e2) {
453: throw new RuntimeException(e2);
454: }
455: }
456: }
|