/* * 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(); } }