001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: /* $Id: LoadQuartzServlet.java 473861 2006-11-12 03:51:14Z gregor $ */
020:
021: package org.apache.lenya.cms.scheduler;
022:
023: import java.io.File;
024: import java.io.FileFilter;
025: import java.io.IOException;
026: import java.io.PrintWriter;
027: import java.util.ArrayList;
028: import java.util.Collections;
029: import java.util.Date;
030: import java.util.Enumeration;
031: import java.util.HashMap;
032: import java.util.Iterator;
033: import java.util.List;
034: import java.util.Map;
035:
036: import javax.servlet.ServletConfig;
037: import javax.servlet.ServletContext;
038: import javax.servlet.ServletException;
039: import javax.servlet.http.HttpServlet;
040: import javax.servlet.http.HttpServletRequest;
041: import javax.servlet.http.HttpServletResponse;
042: import javax.xml.transform.TransformerConfigurationException;
043: import javax.xml.transform.TransformerException;
044:
045: import org.apache.lenya.cms.publication.DocumentBuildException;
046: import org.apache.lenya.cms.publication.DocumentFactory;
047: import org.apache.lenya.cms.publication.DocumentUtil;
048: import org.apache.lenya.cms.publication.PublicationException;
049: import org.apache.lenya.cms.publishing.PublishingEnvironment;
050: import org.apache.lenya.cms.scheduler.xml.TriggerHelper;
051: import org.apache.lenya.util.NamespaceMap;
052: import org.apache.lenya.xml.DocumentHelper;
053: import org.apache.log4j.Logger;
054: import org.quartz.SchedulerException;
055: import org.w3c.dom.Document;
056:
057: /**
058: * A simple servlet that starts an instance of a Quartz scheduler.
059: */
060: public class LoadQuartzServlet extends HttpServlet {
061: /**
062: *
063: */
064: private static final long serialVersionUID = 1L;
065:
066: private static final class IsDirectoryFileFilter implements
067: FileFilter {
068: /**
069: * @see java.io.FileFilter#accept(java.io.File)
070: */
071: public boolean accept(File file) {
072: return file.isDirectory();
073: }
074: }
075:
076: private static Logger log = Logger
077: .getLogger(LoadQuartzServlet.class);
078: private static SchedulerWrapper scheduler = null;
079: private ServletContext servletContext;
080: private String schedulerConfigurations;
081:
082: /**
083: * <code>PREFIX</code> Scheduler namespace prefix
084: */
085: public static final String PREFIX = "scheduler";
086: /**
087: * <code>PARAMETER_ACTION</code> The action parameter
088: */
089: public static final String PARAMETER_ACTION = "action";
090: /**
091: * <code>PARAMETER_PUBLICATION_ID</code> The publication id parameter
092: */
093: public static final String PARAMETER_PUBLICATION_ID = "publication-id";
094: /**
095: * <code>PARAMETER_DOCUMENT_URL</code> The document URL parameter
096: */
097: public static final String PARAMETER_DOCUMENT_URL = "document-url";
098: /**
099: * <code>CONFIGURATION_ELEMENT</code> The configuration element
100: */
101: public static final String CONFIGURATION_ELEMENT = "scheduler-configurations";
102: /**
103: * <code>SERVLET_URL</code> The scheduler servlet URL
104: */
105: public static final String SERVLET_URL = "/servlet/QuartzSchedulerServlet";
106:
107: /**
108: * Returns the scheduler wrapper.
109: * @return A scheduler wrapper.
110: */
111: public static SchedulerWrapper getScheduler() {
112: return scheduler;
113: }
114:
115: /**
116: * Maps servlet context names to servlets.
117: */
118: private static Map servlets = new HashMap();
119:
120: /**
121: * Initializes the servlet.
122: * @param config The servlet configuration.
123: * @throws ServletException when something went wrong.
124: */
125: public void init(ServletConfig config) throws ServletException {
126: super .init(config);
127:
128: this .schedulerConfigurations = config
129: .getInitParameter(CONFIGURATION_ELEMENT);
130: this .servletContext = config.getServletContext();
131:
132: log.debug(".init(): Servlet Context Path: "
133: + getServletContextDirectory().getAbsolutePath());
134:
135: try {
136: log.debug("Storing servlet");
137: String contextPath = getServletContextDirectory()
138: .getCanonicalPath();
139: log.debug(" Context path: [" + contextPath + "]");
140: servlets.put(contextPath, this );
141: } catch (IOException e) {
142: throw new ServletException(e);
143: }
144:
145: log.debug(".init(): Scheduler Configurations: "
146: + this .schedulerConfigurations);
147:
148: try {
149: log.info("Working?...");
150: process();
151: log.info("OK");
152: } catch (Exception e) {
153: log.error("Init of LoadQuartzServlet failed", e);
154: throw new ServletException(e);
155: }
156: }
157:
158: /**
159: * Process.
160: * @throws ServletException when an error occurs.
161: * @throws SchedulerException when an error occurs.
162: */
163: public void process() throws ServletException, SchedulerException {
164: scheduler = new SchedulerWrapper(getServletContextDirectory()
165: .getAbsolutePath(), this .schedulerConfigurations);
166:
167: try {
168: shutdownHook();
169: } catch (Exception e) {
170: log.error(e.toString(), e);
171: throw new ServletException(e);
172: }
173:
174: restoreJobs();
175: }
176:
177: /**
178: * Shuts down the scheduler.
179: */
180: public void destroy() {
181: destroyScheduler();
182: }
183:
184: /**
185: * Shuts down the scheduler.
186: */
187: public static void destroyScheduler() {
188: log.debug("destroy: ");
189: getScheduler().shutdown();
190: }
191:
192: /**
193: * This method sets a ShutdownHook to the system This traps the CTRL+C or kill signal and
194: * shutdows Correctly the system.
195: *
196: * @throws Exception when something went wrong.
197: */
198: public static void shutdownHook() throws Exception {
199: log
200: .debug("-------------------- ShutdownHook --------------------");
201: Runtime.getRuntime().addShutdownHook(new Thread() {
202: public void run() {
203: LoadQuartzServlet.destroyScheduler();
204: }
205: });
206: log
207: .debug("-------------------- End ShutdownHook --------------------");
208: }
209:
210: /**
211: * Handles a GET request.
212: * @param request The request.
213: * @param response The response.
214: * @throws IOException when an error occured.
215: * @throws ServletException when an error occured.
216: */
217: public void doGet(HttpServletRequest request,
218: HttpServletResponse response) throws IOException,
219: ServletException {
220: handleRequest(request, response);
221: }
222:
223: /**
224: * Handles a POST request.
225: * @param req The requust.
226: * @param resp The response.
227: * @throws ServletException when an error occured.
228: * @throws IOException when an error occured.
229: */
230: public void doPost(HttpServletRequest req, HttpServletResponse resp)
231: throws ServletException, IOException {
232: doGet(req, resp);
233: }
234:
235: protected static final String ADD = "add";
236: protected static final String MODIFY = "modify";
237: protected static final String DELETE = "delete";
238: protected static final String DOCUMENT_DELETED = "document-deleted";
239:
240: /**
241: * Handles a servlet request.
242: * @param request The request.
243: * @param response The response.
244: * @throws IOException when something went wrong.
245: */
246: protected void handleRequest(HttpServletRequest request,
247: HttpServletResponse response) throws IOException {
248: log
249: .debug("----------------------------------------------------------------");
250: log.debug("- Incoming request at URI: ");
251: log.debug(request.getServerName() + ":"
252: + request.getServerPort() + request.getRequestURI());
253: log
254: .debug("----------------------------------------------------------------");
255: log.debug("Request parameters:");
256:
257: NamespaceMap schedulerParameters = getSchedulerParameters(request);
258:
259: try {
260: String publicationId = (String) schedulerParameters
261: .get(PARAMETER_PUBLICATION_ID);
262: log.debug("Scheduler invoked.");
263:
264: log.debug("Scheduler Parameters:");
265: log.debug(" scheduler.publication-id: [" + publicationId
266: + "]");
267:
268: logSessionAttributes(request);
269:
270: // check if the request wants to submit, modify or delete a job.
271: String action = (String) schedulerParameters
272: .get(PARAMETER_ACTION);
273: log.debug(" scheduler.action: [" + action + "]");
274: if (action == null) {
275: // do nothing
276: } else if (action.equals(ADD)) {
277: Date startTime = TriggerHelper
278: .getDate(schedulerParameters);
279: getScheduler()
280: .addJob(publicationId, startTime, request);
281: } else if (action.equals(MODIFY)) {
282: Date startTime = TriggerHelper
283: .getDate(schedulerParameters);
284: String jobId = getJobId(schedulerParameters);
285: getScheduler().modifyJob(jobId, publicationId,
286: startTime);
287: } else if (action.equals(DELETE)) {
288: String jobId = getJobId(schedulerParameters);
289: getScheduler().deleteJob(jobId, publicationId);
290: } else if (action.equals(DOCUMENT_DELETED)) {
291: String documentUrl = (String) schedulerParameters
292: .get(PARAMETER_DOCUMENT_URL);
293: DocumentFactory map = DocumentUtil
294: .createDocumentIdentityMap(null, null);
295: org.apache.lenya.cms.publication.Document document = map
296: .getFromURL(documentUrl);
297: deleteDocumentJobs(document);
298: }
299:
300: // handle the remainder of the request by simply returning all
301: // scheduled jobs (for the given publication ID).
302: PrintWriter writer = response.getWriter();
303: response.setContentType("text/xml");
304:
305: Document snapshot = getScheduler().getSnapshot();
306:
307: DocumentHelper.writeDocument(snapshot, writer);
308: } catch (DocumentBuildException e) {
309: log.error("Can't create job snapshot: ", e);
310: throw new IOException(e.getMessage()
311: + " (view log for details)");
312: } catch (TransformerConfigurationException e) {
313: log.error("Can't create job snapshot: ", e);
314: throw new IOException(e.getMessage()
315: + " (view log for details)");
316: } catch (IOException e) {
317: log.error("Can't create job snapshot: ", e);
318: throw new IOException(e.getMessage()
319: + " (view log for details)");
320: } catch (SchedulerException e) {
321: log.error("Can't create job snapshot: ", e);
322: throw new IOException(e.getMessage()
323: + " (view log for details)");
324: } catch (PublicationException e) {
325: log.error("Can't create job snapshot: ", e);
326: throw new IOException(e.getMessage()
327: + " (view log for details)");
328: } catch (TransformerException e) {
329: log.error("Can't create job snapshot: ", e);
330: throw new IOException(e.getMessage()
331: + " (view log for details)");
332: }
333: }
334:
335: /**
336: * Extracts the scheduler parameters from a request.
337: * @param request The request.
338: * @return A namespace map.
339: */
340: public static NamespaceMap getSchedulerParameters(
341: HttpServletRequest request) {
342: Map parameterMap = new HashMap();
343: List keys = new ArrayList();
344: for (Enumeration e = request.getParameterNames(); e
345: .hasMoreElements();) {
346: String key = (String) e.nextElement();
347: keys.add(key);
348: }
349: Collections.sort(keys);
350: for (Iterator i = keys.iterator(); i.hasNext();) {
351: String key = (String) i.next();
352: String[] values = request.getParameterValues(key);
353: log.debug(" [" + key + "] = [" + values[0] + "]");
354: if (values.length == 1) {
355: parameterMap.put(key, values[0]);
356: } else {
357: parameterMap.put(key, values);
358: }
359: }
360:
361: NamespaceMap schedulerParameters = new NamespaceMap(
362: parameterMap, PREFIX);
363: return schedulerParameters;
364: }
365:
366: /**
367: * Deletes
368: * @param document
369: * @throws DocumentBuildException
370: * @throws SchedulerException
371: * @throws PublicationException
372: */
373: public void deleteDocumentJobs(
374: org.apache.lenya.cms.publication.Document document)
375: throws DocumentBuildException, SchedulerException,
376: PublicationException {
377: log.debug("Requested to delete jobs for document URL ["
378: + document.getCanonicalWebappURL() + "]");
379: getScheduler().deleteJobs(document);
380: }
381:
382: /**
383: * Extracts the job ID from the scheduler parameters.
384: * @param schedulerParameters A namespace map.
385: * @return A string.
386: */
387: protected String getJobId(NamespaceMap schedulerParameters) {
388: String parameterName = NamespaceMap.getFullName(
389: SchedulerWrapper.JOB_PREFIX, SchedulerWrapper.JOB_ID);
390: String jobId = (String) schedulerParameters.get(parameterName);
391: log.debug(" scheduler.job.id: [" + jobId + "]");
392: return jobId;
393: }
394:
395: /**
396: * Logs the session attributes of a request.
397: * @param request The request.
398: */
399: protected void logSessionAttributes(HttpServletRequest request) {
400: log
401: .debug("-------------------- Session Attributes --------------------");
402: for (Enumeration e = request.getSession().getAttributeNames(); e
403: .hasMoreElements();) {
404: String name = (String) e.nextElement();
405: log.debug(name + " = "
406: + request.getSession().getAttribute(name));
407: }
408: log
409: .debug("-------------------- End Session Attributes --------------------");
410: }
411:
412: /**
413: * Returns the servlet context path.
414: *
415: * @return A string.
416: */
417: public File getServletContextDirectory() {
418: return new File(this .servletContext.getRealPath("/"));
419: }
420:
421: /**
422: * Restores the jobs.
423: * @throws SchedulerException when something went wrong.
424: */
425: public void restoreJobs() throws SchedulerException {
426:
427: File publicationsDirectory = new File(
428: getServletContextDirectory(),
429: PublishingEnvironment.PUBLICATION_PREFIX);
430:
431: File[] publicationDirectories = publicationsDirectory
432: .listFiles(new IsDirectoryFileFilter());
433:
434: log.debug("=========================================");
435: log.debug(" Restoring jobs.");
436: log.debug(" servlet context: ["
437: + getServletContextDirectory() + "]");
438: log.debug(" publications directory: ["
439: + publicationsDirectory + "]");
440: log.debug("=========================================");
441:
442: for (int i = 0; i < publicationDirectories.length; i++) {
443: File directory = publicationDirectories[i];
444: String publicationId = directory.getName();
445: /*
446: PublicationManagerImpl factory = PublicationManagerImpl.getInstance(new ConsoleLogger());
447: Publication publication;
448: try {
449: publication = factory.getPublication(publicationId, getServletContextDirectory());
450: } catch (PublicationException e) {
451: throw new SchedulerException(e);
452: }
453: if (publication.exists()) {
454: getScheduler().restoreJobs(publicationId);
455: }
456: */
457: }
458: }
459:
460: /**
461: * Returns the servlet for a certain canonical servlet context path.
462: * @param contextPath The canonical servlet context path.
463: * @return A LoadQuartzServlet.
464: */
465: public static LoadQuartzServlet getServlet(String contextPath) {
466: return (LoadQuartzServlet) servlets.get(contextPath);
467: }
468:
469: /**
470: * Generates the request URI needed to delete the jobs for a certain document.
471: * @param port The port of the servlet
472: * @param servletContextPath The context path of the servlet
473: * @param document The document.
474: * @return A string.
475: */
476: public static String getDeleteDocumentRequestURI(String port,
477: String servletContextPath,
478: org.apache.lenya.cms.publication.Document document) {
479:
480: NamespaceMap requestParameters = new NamespaceMap(PREFIX);
481: requestParameters.put(PARAMETER_ACTION, DOCUMENT_DELETED);
482: requestParameters.put(PARAMETER_PUBLICATION_ID, document
483: .getPublication().getId());
484: requestParameters.put(PARAMETER_DOCUMENT_URL, document
485: .getCanonicalWebappURL());
486:
487: StringBuffer buf = new StringBuffer();
488: buf.append("http://127.0.0.1:" + port + servletContextPath
489: + "?");
490: Map map = requestParameters.getMap();
491:
492: String[] keys = (String[]) map.keySet().toArray(
493: new String[map.keySet().size()]);
494: for (int i = 0; i < keys.length; i++) {
495: if (i > 0) {
496: buf.append("&");
497: }
498: String value = (String) map.get(keys[i]);
499: buf.append(keys[i] + "=" + value);
500: }
501:
502: return buf.toString();
503: }
504: }
|