blob: f5445927d9530c1ddf8a9084027725b6267f2356 [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.
*/
package org.apache.cocoon.generation;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.ResourceNotFoundException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.components.NekoHtmlSaxParser;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.environment.http.HttpEnvironment;
import org.apache.cocoon.util.PostInputStream;
import org.apache.cocoon.xml.dom.DOMBuilder;
import org.apache.cocoon.xml.dom.DOMStreamer;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.xml.xpath.XPathProcessor;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* @cocoon.sitemap.component.documentation
* The Neko HTML generator reads HTML from a source, converts it to XHTML
* and generates SAX Events. It uses the NekoHTML library to do this.
*
* @cocoon.sitemap.component.name nekohtml
* @cocoon.sitemap.component.label content
* @cocoon.sitemap.component.logger sitemap.generator.nekohtml
* @cocoon.sitemap.component.documentation.caching Yes.
* Uses the last modification date of the xml document for validation
*
* @cocoon.sitemap.component.pooling.max 32
*
* @author <a href="mailto:dims@yahoo.com">Davanum Srinivas</a>
* @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
* @author <a href="mailto:barozzi@nicolaken.com">Nicola Ken Barozzi</a>
* @author <a href="mailto:gianugo@apache.org">Gianugo Rabellino</a>
*
* @version CVS $Id$
*/
public class NekoHTMLGenerator extends ServiceableGenerator
implements Configurable, CacheableProcessingComponent, Disposable {
/** The parameter that specifies what request parameter to use, if any */
public static final String FORM_NAME = "form-name";
/** The request parameter value, if coming from a request parameter */
private String requestParameterValue;
/** The source, if coming from a file */
private Source inputSource;
/** The source, if coming from the request */
private InputStream requestStream;
/** XPATH expression */
private String xpath;
/** XPath Processor */
private XPathProcessor processor;
/** Neko properties */
private Properties properties;
public void service(ServiceManager manager)
throws ServiceException {
super.service(manager);
this.processor = (XPathProcessor) this.manager.lookup(XPathProcessor.ROLE);
}
public void configure(Configuration config) throws ConfigurationException {
String configUrl = config.getChild("neko-config").getValue(null);
if (configUrl != null) {
org.apache.excalibur.source.SourceResolver resolver = null;
Source configSource = null;
try {
resolver = (org.apache.excalibur.source.SourceResolver)this.manager.lookup(org.apache.excalibur.source.SourceResolver.ROLE);
configSource = resolver.resolveURI(configUrl);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Loading configuration from " + configSource.getURI());
}
this.properties = new Properties();
this.properties.load(configSource.getInputStream());
} catch (Exception e) {
getLogger().warn("Cannot load configuration from " + configUrl);
throw new ConfigurationException("Cannot load configuration from " + configUrl, e);
} finally {
if ( null != resolver ) {
this.manager.release(resolver);
resolver.release(configSource);
}
}
}
}
/**
* Recycle this component.
* All instance variables are set to <code>null</code>.
*/
public void recycle() {
if (this.requestStream != null) {
try {
this.requestStream.close();
} catch (IOException e) {
// ignore
}
this.requestStream = null;
}
if (this.inputSource != null) {
this.resolver.release(this.inputSource);
this.inputSource = null;
}
this.requestParameterValue = null;
this.xpath = null;
super.recycle();
}
/**
* Setup the html generator.
* Try to get the last modification date of the source for caching.
*/
public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
throws ProcessingException, SAXException, IOException {
super.setup(resolver, objectModel, src, par);
Request request = ObjectModelHelper.getRequest(objectModel);
if (this.source == null) {
// Handle this request as the StreamGenerator does (from the POST
// request or from a request parameter), but try to make sure
// that the output will be well-formed
String contentType = request.getContentType();
if (contentType == null ) {
throw new IOException("Content-type was not specified for this request");
} else if (contentType.startsWith("application/x-www-form-urlencoded")
|| contentType.startsWith("multipart/form-data")) {
String requested = parameters.getParameter(FORM_NAME, null);
if (requested == null) {
throw new ProcessingException(
"NekoHtmlGenerator with no \"src\" parameter expects a sitemap parameter called '" +
FORM_NAME + "' for handling form data"
);
}
requestParameterValue = request.getParameter(requested);
} else if (contentType.startsWith("text/plain")
|| contentType.startsWith("text/xml")
|| contentType.startsWith("application/xml")) {
HttpServletRequest httpRequest = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT);
if ( httpRequest == null ) {
throw new ProcessingException("This functionality only works in an http environment.");
}
int len = request.getContentLength();
if (len > 0) {
requestStream = new PostInputStream(httpRequest.getInputStream(), len);
} else {
throw new IOException("getContentLen() == 0");
}
} else {
throw new IOException("Unexpected getContentType(): " + request.getContentType());
}
} else {
// append the request parameter to the URL if necessary
if (parameters.getParameterAsBoolean("copy-parameters", false)
&& request.getQueryString() != null) {
StringBuffer query = new StringBuffer(this.source);
query.append(this.source.indexOf("?") == -1 ? '?' : '&');
query.append(request.getQueryString());
this.source = query.toString();
}
try {
this.inputSource = resolver.resolveURI(this.source);
} catch (SourceException se) {
throw SourceUtil.handle("Unable to resolve " + this.source, se);
}
}
xpath = request.getParameter("xpath");
if (xpath == null) {
xpath = par.getParameter("xpath",null);
}
}
/**
* Generate the unique key.
* This key must be unique inside the space of this component.
* This method must be invoked before the generateValidity() method.
*
* @return The generated key or <code>0</code> if the component
* is currently not cacheable.
*/
public java.io.Serializable getKey() {
if (this.inputSource == null)
return null;
if (this.xpath != null) {
StringBuffer buffer = new StringBuffer(this.inputSource.getURI());
buffer.append(':').append(this.xpath);
return buffer.toString();
} else {
return this.inputSource.getURI();
}
}
/**
* Generate the validity object.
* Before this method can be invoked the generateKey() method
* must be invoked.
*
* @return The generated validity object or <code>null</code> if the
* component is currently not cacheable.
*/
public SourceValidity getValidity() {
if (this.inputSource == null)
return null;
return this.inputSource.getValidity();
}
/**
* Generate XML data.
*/
public void generate()
throws IOException, SAXException, ProcessingException {
try {
NekoHtmlSaxParser parser = new NekoHtmlSaxParser(this.properties);
InputSource saxSource;
if (this.requestParameterValue != null) {
saxSource = new InputSource(new StringReader(this.requestParameterValue));
}
else {
if (inputSource != null) {
requestStream = this.inputSource.getInputStream();
}
saxSource = new InputSource(requestStream);
}
if (xpath != null) {
DOMBuilder builder = new DOMBuilder();
parser.setContentHandler(builder);
parser.parse(saxSource);
Document doc = builder.getDocument();
DOMStreamer domStreamer = new DOMStreamer(this.contentHandler,
this.lexicalHandler);
this.contentHandler.startDocument();
NodeList nl = processor.selectNodeList(doc, xpath);
int length = nl.getLength();
for(int i=0; i < length; i++) {
domStreamer.stream(nl.item(i));
}
this.contentHandler.endDocument();
} else {
parser.setContentHandler(this.contentHandler);
parser.parse(saxSource);
}
} catch (IOException e){
throw new ResourceNotFoundException("Could not get resource "
+ this.inputSource.getURI(), e);
} catch (SAXException e){
throw e;
} catch (Exception e){
throw new ProcessingException("Exception in NekoHTMLGenerator.generate()",e);
}
}
public void dispose() {
if (this.manager != null) {
this.manager.release(this.processor);
this.manager = null;
}
this.processor = null;
super.dispose();
}
}