/*
* 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.
*/
package org.apache.cocoon.servlet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.cocoon.configuration.Settings;
import org.apache.cocoon.servlet.node.LastModifiedCollector;
import org.apache.cocoon.servlet.node.MimeTypeCollector;
import org.apache.cocoon.servlet.node.StatusCodeCollector;
import org.apache.cocoon.servlet.util.HttpContextHelper;
import org.apache.cocoon.servlet.util.ManifestUtils;
import org.apache.cocoon.servlet.util.ObjectModelProvider;
import org.apache.cocoon.servlet.util.SettingsHelper;
import org.apache.cocoon.sitemap.Invocation;
import org.apache.cocoon.sitemap.InvocationImpl;
import org.apache.cocoon.sitemap.SitemapBuilder;
import org.apache.cocoon.sitemap.node.Sitemap;
import org.apache.cocoon.spring.configurator.ResourceUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
public class XMLSitemapServlet extends HttpServlet implements BeanFactoryAware {
private static final long serialVersionUID = 1L;
private BeanFactory beanFactory;
private boolean initialized;
private final Log logger = LogFactory.getLog(this.getClass());
private ServletConfig servletConfig;
private Sitemap sitemap;
private String version = "";
@Override
public void init(ServletConfig servletConfig) throws ServletException {
this.servletConfig = servletConfig;
super.init(this.servletConfig);
this.initVersionNumber();
}
public void invoke(String requestURI, Map<String, Object> parameters, OutputStream outputStream)
throws ServletException {
InvocationImpl invocation = (InvocationImpl) this.beanFactory.getBean(Invocation.class.getName());
invocation.setBaseURL(this.getBaseURL());
invocation.setRequestURI(requestURI);
invocation.setParameters(parameters);
invocation.setOutputStream(outputStream);
invocation.setObjectModel(ObjectModelProvider.provide(parameters));
this.sitemap.invoke(invocation);
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
long start = System.nanoTime();
this.logRequest(request);
this.lazyInitialize(this.servletConfig);
try {
Settings settings = (Settings) this.beanFactory.getBean(Settings.class.getName());
SitemapDelegator.setSitemapServlet(this);
MimeTypeCollector.clearMimeType();
StatusCodeCollector.clearStatusCode();
LastModifiedCollector.clearLastModified();
// assemble parameters
Map<String, Object> parameters = this.getInvocationParameters(request);
HttpContextHelper.storeRequest(request, parameters);
HttpContextHelper.storeResponse(response, parameters);
HttpContextHelper.storeServletContext(this.getServletContext(), parameters);
SettingsHelper.storeSettings(settings, parameters);
// invoke the sitemap engine
ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
this.invoke(this.calcSitemapRequestURI(request), parameters, baos);
// collect meta information from the previous run of the sitemap engine
long lastModified = LastModifiedCollector.getLastModified();
String mimeType = MimeTypeCollector.getMimeType();
int contentLengh = baos.size();
int statusCode = StatusCodeCollector.getStatusCode();
// send the Last-Modified header
if (lastModified > -1) {
response.setDateHeader("Last-Modified", lastModified);
}
// set the X-Cocoon-Version header
if (!"false".equals(settings.getProperty("org.apache.cocoon.show-version"))) {
response.setHeader("X-Cocoon-Version", this.version);
}
// conditional request support
long ifLastModifiedSince = request.getDateHeader("If-Modified-Since");
if (ifLastModifiedSince > 0 && ifLastModifiedSince / 1000 >= lastModified / 1000 && lastModified > 0) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
if (this.logger.isInfoEnabled()) {
this.logger.info("The requested resource " + request.getRequestURI()
+ " hasn't changed: ifLastModifiedSince=" + ifLastModifiedSince + ", lastModified="
+ lastModified + ". Hence 304 (NOT_MODIFIED) was sent as status code.");
}
return;
}
// write the sitemap result to the output stream
if (mimeType == null || "".equals(mimeType) || "content/unknown".equals(mimeType)) {
mimeType = this.servletConfig.getServletContext().getMimeType(request.getRequestURI());
}
if (mimeType != null) {
response.setContentType(mimeType);
}
response.setStatus(statusCode);
if (contentLengh > 0) {
response.setContentLength(contentLengh);
if (this.logger.isInfoEnabled()) {
this.logger.info("Going to send response: mimeType=" + mimeType + ", contentLength=" + contentLengh
+ ", statusCode=" + statusCode + ", lastModified=" + lastModified);
}
response.getOutputStream().write(baos.toByteArray());
}
} catch (Exception e) {
throw this.wrapException(e, "An exception occurred while executing the sitemap.");
} finally {
SitemapDelegator.removeSitemapServlet();
this.logger.info("Sitemap execution took " + (System.nanoTime() - start) / 1000000f + " ms.");
}
}
private String calcSitemapRequestURI(HttpServletRequest request) {
String contextPath = request.getContextPath();
String mountPath = request.getServletPath();
return request.getRequestURI().substring(contextPath.length() + mountPath.length());
}
private URL getBaseURL() throws ServletException {
try {
return this.servletConfig.getServletContext().getResource("/");
} catch (MalformedURLException e) {
throw this.wrapException(e, "An exception occurred while retrieving the base "
+ "URL from the servlet context.");
}
}
@SuppressWarnings("unchecked")
private Map<String, Object> getInvocationParameters(HttpServletRequest req) {
Map<String, Object> invocationParameters = new HashMap<String, Object>();
for (Enumeration<String> names = req.getParameterNames(); names.hasMoreElements();) {
String name = names.nextElement();
invocationParameters.put(name, req.getParameter(name));
}
return invocationParameters;
}
private String getSitemapPath() {
String sitemapPath = this.getInitParameter("sitemap-path");
if (sitemapPath == null) {
sitemapPath = "/sitemap.xmap";
}
if (!sitemapPath.startsWith("/")) {
sitemapPath = "/" + sitemapPath;
}
return sitemapPath;
}
/**
* Read versioning information from the Cocoon 3 Servlet module
*/
private void initVersionNumber() {
String servletModuleVersion = ResourceUtils.getPOMProperties("org.apache.cocoon.servlet", "cocoon-servlet")
.getProperty("version");
if (servletModuleVersion != null) {
this.version = servletModuleVersion;
}
if (this.version.endsWith("SNAPSHOT")) {
String buildNumber = "";
try {
String buildNumberAttr = ManifestUtils.getAttribute(this.getClass(), "Implementation-Build");
if (buildNumberAttr != null && !"".equals(buildNumberAttr) && !"na".equals(buildNumberAttr)) {
buildNumber = "/rev" + buildNumberAttr;
}
} catch (IOException e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Error while reading an attribute from the manifest.", e);
}
}
this.version += buildNumber;
}
}
private synchronized void lazyInitialize(ServletConfig servletConfig) throws ServletException {
if (!this.initialized) {
try {
SitemapBuilder sitemapBuilder = (SitemapBuilder) this.beanFactory.getBean(SitemapBuilder.class
.getName());
URL url = servletConfig.getServletContext().getResource(this.getSitemapPath());
this.sitemap = sitemapBuilder.build(url);
} catch (Exception e) {
throw this.wrapException(e, "An exception occurred while building the sitemap.");
}
this.initialized = true;
}
}
private void logRequest(HttpServletRequest request) throws ServletException {
if (this.logger.isInfoEnabled()) {
this.logger.info("Performing " + request.getMethod().toUpperCase() + " request at "
+ request.getRequestURI());
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("The base URL for this request is " + this.getBaseURL());
}
}
private ServletException wrapException(Exception e, String msg) throws ServletException {
this.logger.error(msg, e);
ServletException servletException = new ServletException(msg, e);
if (servletException.getCause() == null) {
servletException.initCause(e);
}
return servletException;
}
}