blob: a49dbfdc0d5ce74e9656cb43b5193b908adc6313 [file] [log] [blame]
/*
* 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.
*
*/
/* $Id$ */
package org.apache.lenya.cms.scheduler;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import org.apache.lenya.cms.publication.DocumentBuildException;
import org.apache.lenya.cms.publication.DocumentFactory;
import org.apache.lenya.cms.publication.DocumentUtil;
import org.apache.lenya.cms.publication.PublicationException;
import org.apache.lenya.cms.publishing.PublishingEnvironment;
import org.apache.lenya.cms.scheduler.xml.TriggerHelper;
import org.apache.lenya.util.NamespaceMap;
import org.apache.lenya.xml.DocumentHelper;
import org.apache.log4j.Logger;
import org.quartz.SchedulerException;
import org.w3c.dom.Document;
/**
* A simple servlet that starts an instance of a Quartz scheduler.
*/
public class LoadQuartzServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
private static final class IsDirectoryFileFilter implements FileFilter {
/**
* @see java.io.FileFilter#accept(java.io.File)
*/
public boolean accept(File file) {
return file.isDirectory();
}
}
private static Logger log = Logger.getLogger(LoadQuartzServlet.class);
private static SchedulerWrapper scheduler = null;
private ServletContext servletContext;
private String schedulerConfigurations;
/**
* <code>PREFIX</code> Scheduler namespace prefix
*/
public static final String PREFIX = "scheduler";
/**
* <code>PARAMETER_ACTION</code> The action parameter
*/
public static final String PARAMETER_ACTION = "action";
/**
* <code>PARAMETER_PUBLICATION_ID</code> The publication id parameter
*/
public static final String PARAMETER_PUBLICATION_ID = "publication-id";
/**
* <code>PARAMETER_DOCUMENT_URL</code> The document URL parameter
*/
public static final String PARAMETER_DOCUMENT_URL = "document-url";
/**
* <code>CONFIGURATION_ELEMENT</code> The configuration element
*/
public static final String CONFIGURATION_ELEMENT = "scheduler-configurations";
/**
* <code>SERVLET_URL</code> The scheduler servlet URL
*/
public static final String SERVLET_URL = "/servlet/QuartzSchedulerServlet";
/**
* Returns the scheduler wrapper.
* @return A scheduler wrapper.
*/
public static SchedulerWrapper getScheduler() {
return scheduler;
}
/**
* Maps servlet context names to servlets.
*/
private static Map servlets = new HashMap();
/**
* Initializes the servlet.
* @param config The servlet configuration.
* @throws ServletException when something went wrong.
*/
public void init(ServletConfig config) throws ServletException {
super.init(config);
this.schedulerConfigurations = config.getInitParameter(CONFIGURATION_ELEMENT);
this.servletContext = config.getServletContext();
log.debug(".init(): Servlet Context Path: "
+ getServletContextDirectory().getAbsolutePath());
try {
log.debug("Storing servlet");
String contextPath = getServletContextDirectory().getCanonicalPath();
log.debug(" Context path: [" + contextPath + "]");
servlets.put(contextPath, this);
} catch (IOException e) {
throw new ServletException(e);
}
log.debug(".init(): Scheduler Configurations: " + this.schedulerConfigurations);
try {
log.info("Working?...");
process();
log.info("OK");
} catch (Exception e) {
log.error("Init of LoadQuartzServlet failed", e);
throw new ServletException(e);
}
}
/**
* Process.
* @throws ServletException when an error occurs.
* @throws SchedulerException when an error occurs.
*/
public void process() throws ServletException, SchedulerException {
scheduler = new SchedulerWrapper(getServletContextDirectory().getAbsolutePath(),
this.schedulerConfigurations);
try {
shutdownHook();
} catch (Exception e) {
log.error(e.toString(), e);
throw new ServletException(e);
}
restoreJobs();
}
/**
* Shuts down the scheduler.
*/
public void destroy() {
destroyScheduler();
}
/**
* Shuts down the scheduler.
*/
public static void destroyScheduler() {
log.debug("destroy: ");
getScheduler().shutdown();
}
/**
* This method sets a ShutdownHook to the system This traps the CTRL+C or kill signal and
* shutdows Correctly the system.
*
* @throws Exception when something went wrong.
*/
public static void shutdownHook() throws Exception {
log.debug("-------------------- ShutdownHook --------------------");
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
LoadQuartzServlet.destroyScheduler();
}
});
log.debug("-------------------- End ShutdownHook --------------------");
}
/**
* Handles a GET request.
* @param request The request.
* @param response The response.
* @throws IOException when an error occured.
* @throws ServletException when an error occured.
*/
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
handleRequest(request, response);
}
/**
* Handles a POST request.
* @param req The requust.
* @param resp The response.
* @throws ServletException when an error occured.
* @throws IOException when an error occured.
*/
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
doGet(req, resp);
}
protected static final String ADD = "add";
protected static final String MODIFY = "modify";
protected static final String DELETE = "delete";
protected static final String DOCUMENT_DELETED = "document-deleted";
/**
* Handles a servlet request.
* @param request The request.
* @param response The response.
* @throws IOException when something went wrong.
*/
protected void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws IOException {
log.debug("----------------------------------------------------------------");
log.debug("- Incoming request at URI: ");
log
.debug(request.getServerName() + ":" + request.getServerPort()
+ request.getRequestURI());
log.debug("----------------------------------------------------------------");
log.debug("Request parameters:");
NamespaceMap schedulerParameters = getSchedulerParameters(request);
try {
String publicationId = (String) schedulerParameters.get(PARAMETER_PUBLICATION_ID);
log.debug("Scheduler invoked.");
log.debug("Scheduler Parameters:");
log.debug(" scheduler.publication-id: [" + publicationId + "]");
logSessionAttributes(request);
// check if the request wants to submit, modify or delete a job.
String action = (String) schedulerParameters.get(PARAMETER_ACTION);
log.debug(" scheduler.action: [" + action + "]");
if (action == null) {
// do nothing
} else if (action.equals(ADD)) {
Date startTime = TriggerHelper.getDate(schedulerParameters);
getScheduler().addJob(publicationId, startTime, request);
} else if (action.equals(MODIFY)) {
Date startTime = TriggerHelper.getDate(schedulerParameters);
String jobId = getJobId(schedulerParameters);
getScheduler().modifyJob(jobId, publicationId, startTime);
} else if (action.equals(DELETE)) {
String jobId = getJobId(schedulerParameters);
getScheduler().deleteJob(jobId, publicationId);
} else if (action.equals(DOCUMENT_DELETED)) {
String documentUrl = (String) schedulerParameters.get(PARAMETER_DOCUMENT_URL);
DocumentFactory map = DocumentUtil.createDocumentIdentityMap(null, null);
org.apache.lenya.cms.publication.Document document = map.getFromURL(documentUrl);
deleteDocumentJobs(document);
}
// handle the remainder of the request by simply returning all
// scheduled jobs (for the given publication ID).
PrintWriter writer = response.getWriter();
response.setContentType("text/xml");
Document snapshot = getScheduler().getSnapshot();
DocumentHelper.writeDocument(snapshot, writer);
} catch (DocumentBuildException e) {
log.error("Can't create job snapshot: ", e);
throw new IOException(e.getMessage() + " (view log for details)");
} catch (TransformerConfigurationException e) {
log.error("Can't create job snapshot: ", e);
throw new IOException(e.getMessage() + " (view log for details)");
} catch (IOException e) {
log.error("Can't create job snapshot: ", e);
throw new IOException(e.getMessage() + " (view log for details)");
} catch (SchedulerException e) {
log.error("Can't create job snapshot: ", e);
throw new IOException(e.getMessage() + " (view log for details)");
} catch (PublicationException e) {
log.error("Can't create job snapshot: ", e);
throw new IOException(e.getMessage() + " (view log for details)");
} catch (TransformerException e) {
log.error("Can't create job snapshot: ", e);
throw new IOException(e.getMessage() + " (view log for details)");
}
}
/**
* Extracts the scheduler parameters from a request.
* @param request The request.
* @return A namespace map.
*/
public static NamespaceMap getSchedulerParameters(HttpServletRequest request) {
Map parameterMap = new HashMap();
List keys = new ArrayList();
for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) {
String key = (String) e.nextElement();
keys.add(key);
}
Collections.sort(keys);
for (Iterator i = keys.iterator(); i.hasNext();) {
String key = (String) i.next();
String[] values = request.getParameterValues(key);
log.debug(" [" + key + "] = [" + values[0] + "]");
if (values.length == 1) {
parameterMap.put(key, values[0]);
} else {
parameterMap.put(key, values);
}
}
NamespaceMap schedulerParameters = new NamespaceMap(parameterMap, PREFIX);
return schedulerParameters;
}
/**
* Deletes
* @param document
* @throws DocumentBuildException
* @throws SchedulerException
* @throws PublicationException
*/
public void deleteDocumentJobs(org.apache.lenya.cms.publication.Document document)
throws DocumentBuildException, SchedulerException, PublicationException {
log.debug("Requested to delete jobs for document URL [" + document.getCanonicalWebappURL()
+ "]");
getScheduler().deleteJobs(document);
}
/**
* Extracts the job ID from the scheduler parameters.
* @param schedulerParameters A namespace map.
* @return A string.
*/
protected String getJobId(NamespaceMap schedulerParameters) {
String parameterName = NamespaceMap.getFullName(SchedulerWrapper.JOB_PREFIX,
SchedulerWrapper.JOB_ID);
String jobId = (String) schedulerParameters.get(parameterName);
log.debug(" scheduler.job.id: [" + jobId + "]");
return jobId;
}
/**
* Logs the session attributes of a request.
* @param request The request.
*/
protected void logSessionAttributes(HttpServletRequest request) {
log.debug("-------------------- Session Attributes --------------------");
for (Enumeration e = request.getSession().getAttributeNames(); e.hasMoreElements();) {
String name = (String) e.nextElement();
log.debug(name + " = " + request.getSession().getAttribute(name));
}
log.debug("-------------------- End Session Attributes --------------------");
}
/**
* Returns the servlet context path.
*
* @return A string.
*/
public File getServletContextDirectory() {
return new File(this.servletContext.getRealPath("/"));
}
/**
* Restores the jobs.
* @throws SchedulerException when something went wrong.
*/
public void restoreJobs() throws SchedulerException {
File publicationsDirectory = new File(getServletContextDirectory(),
PublishingEnvironment.PUBLICATION_PREFIX);
File[] publicationDirectories = publicationsDirectory
.listFiles(new IsDirectoryFileFilter());
log.debug("=========================================");
log.debug(" Restoring jobs.");
log.debug(" servlet context: [" + getServletContextDirectory() + "]");
log.debug(" publications directory: [" + publicationsDirectory + "]");
log.debug("=========================================");
for (int i = 0; i < publicationDirectories.length; i++) {
File directory = publicationDirectories[i];
String publicationId = directory.getName();
/*
PublicationManagerImpl factory = PublicationManagerImpl.getInstance(new ConsoleLogger());
Publication publication;
try {
publication = factory.getPublication(publicationId, getServletContextDirectory());
} catch (PublicationException e) {
throw new SchedulerException(e);
}
if (publication.exists()) {
getScheduler().restoreJobs(publicationId);
}
*/
}
}
/**
* Returns the servlet for a certain canonical servlet context path.
* @param contextPath The canonical servlet context path.
* @return A LoadQuartzServlet.
*/
public static LoadQuartzServlet getServlet(String contextPath) {
return (LoadQuartzServlet) servlets.get(contextPath);
}
/**
* Generates the request URI needed to delete the jobs for a certain document.
* @param port The port of the servlet
* @param servletContextPath The context path of the servlet
* @param document The document.
* @return A string.
*/
public static String getDeleteDocumentRequestURI(String port, String servletContextPath,
org.apache.lenya.cms.publication.Document document) {
NamespaceMap requestParameters = new NamespaceMap(PREFIX);
requestParameters.put(PARAMETER_ACTION, DOCUMENT_DELETED);
requestParameters.put(PARAMETER_PUBLICATION_ID, document.getPublication().getId());
requestParameters.put(PARAMETER_DOCUMENT_URL, document.getCanonicalWebappURL());
StringBuffer buf = new StringBuffer();
buf.append("http://127.0.0.1:" + port + servletContextPath + "?");
Map map = requestParameters.getMap();
String[] keys = (String[]) map.keySet().toArray(new String[map.keySet().size()]);
for (int i = 0; i < keys.length; i++) {
if (i > 0) {
buf.append("&");
}
String value = (String) map.get(keys[i]);
buf.append(keys[i] + "=" + value);
}
return buf.toString();
}
}