/*
* Copyright 2011 cruxframework.org.
*
* Licensed 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.cruxframework.crux.core.declarativeui;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cruxframework.crux.core.config.ConfigurationFactory;
import org.cruxframework.crux.core.declarativeui.conditional.IfDevicePreProcessor;
import org.cruxframework.crux.core.declarativeui.template.TemplatesPreProcessor;
import org.cruxframework.crux.core.declarativeui.view.ViewLoader;
import org.cruxframework.crux.core.server.Environment;
import org.cruxframework.crux.core.utils.RegexpPatterns;
import org.cruxframework.crux.core.utils.StreamUtils;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Process Crux view files, extracting metadata and generating the host html for
* application pages.
*
* @author Thiago da Rosa de Bustamante
*/
public class ViewProcessor
{
private static DocumentBuilder documentBuilder = null;
// Makes it easier to read the output files
private static boolean forceIndent = false;
private static final Lock lock = new ReentrantLock();
private static final Log log = LogFactory.getLog(ViewProcessor.class);
private static String outputCharset = "UTF-8";
private List<CruxXmlPreProcessor> preProcessors;
public ViewProcessor(ViewLoader viewProvider)
{
initDocumentBuilder();
initPreprocessors(viewProvider);
}
private void initPreprocessors(ViewLoader viewProvider)
{
preProcessors = new ArrayList<CruxXmlPreProcessor>();
if (viewProvider != null)
{
preProcessors.add(new TemplatesPreProcessor(viewProvider.getTemplateLoader()));
preProcessors.add(new IfDevicePreProcessor());
}
String xmlPreProcessors = ConfigurationFactory.getConfigurations().cruxXmlPreProcessors();
if (xmlPreProcessors != null && xmlPreProcessors.trim().length() > 0)
{
try
{
String[] processors = RegexpPatterns.REGEXP_COMMA.split(xmlPreProcessors);
for(String processor: processors)
{
Class<?> processorClass = Class.forName(processor.trim());
preProcessors.add((CruxXmlPreProcessor) processorClass.newInstance());
}
}
catch (Exception e)
{
log.error("Error registering a custom cruxXmlPreProcessor. "
+ "Please check your this expression on your Crux.properties file: ["
+ xmlPreProcessors + "]. Message: " + e.getMessage(), e);
}
}
}
/**
* Extract the widgets metadata from the view page.
*
* @param viewId
* @param device
* @param viewSource
* @param xhmltInput
* @return
*/
public JSONObject extractWidgetsMetadata(String viewId, String device, Document viewSource, boolean xhmltInput)
{
try
{
ViewParser viewParser = new ViewParser(viewId, device, true, mustIndent(), xhmltInput);
String metadata = viewParser.extractCruxMetaData(viewSource);
return new JSONObject(metadata);
}
catch (Exception e)
{
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* Generate the HTML code from the view page.
*
* @param viewId
* @param device
* @param view
* @param out
*/
public void generateHTML(String viewId, String device, Document view, OutputStream out)
{
try
{
StringWriter buff = new StringWriter();
ViewParser viewParser = new ViewParser(viewId, device, false, mustIndent(), true);
viewParser.generateHTMLHostPage(view, buff);
String result = buff.toString();
String outCharset = getOutputCharset();
if (outCharset == null || outCharset.length() == 0)
{
throw new DeclarativeUITransformerException("Outputcharset is undefined. Check your web.xml file to ensure that DevModeInitializerListener is correctly configured.");
}
StreamUtils.write(new ByteArrayInputStream(result.getBytes(outCharset)), out, false);
}
catch (Exception e)
{
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* @return
*/
public String getOutputCharset()
{
return outputCharset;
}
/**
*
* @param file
* @param device
* @return
*/
public Document getView(InputStream file, String filename, String device)
{
if (file == null)
{
return null;
}
return loadCruxPage(file, filename, device);
}
/**
* Makes it easier to read the output files
* @param force
*/
public void setForceIndent(boolean force)
{
forceIndent = force;
}
/**
* @param outputCharset
*/
public void setOutputCharset(String charset)
{
outputCharset = charset;
}
/**
* Loads Crux view page
* @param fileName
* @return
* @throws ViewParserException
*/
private Document loadCruxPage(InputStream file, String filename, String device)
{
try
{
Document document = documentBuilder.parse(file);
return preprocess(document, device);
}
catch (Exception e)
{
log.error("Error parsing file: ["+filename+"] for DeviceAdaptive interface ["+device+"]: " + e.getMessage(), e);
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* @return
*/
private boolean mustIndent()
{
return !Environment.isProduction() || forceIndent;
}
/**
*
* @param doc
* @return
*/
private Document preprocess(Document doc, String device)
{
for (CruxXmlPreProcessor preProcessor : preProcessors)
{
doc = preProcessor.preprocess(doc, device);
}
return doc;
}
/**
* Initializes the static resources
*/
private static void initDocumentBuilder()
{
if (documentBuilder == null)
{
lock.lock();
if (documentBuilder == null)
{
try
{
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
builderFactory.setIgnoringComments(true);
builderFactory.setIgnoringElementContentWhitespace(true);
documentBuilder = builderFactory.newDocumentBuilder();
documentBuilder.setEntityResolver(new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
if (systemId.contains("crux-view.dtd"))
{
return new InputSource(new ByteArrayInputStream(getValidEntities().getBytes()));
}
else
{
return null;
}
}
private String getValidEntities() {
StringBuffer sb = new StringBuffer();
sb.append("<!ENTITY quot \""\">");
sb.append("<!ENTITY amp \"&\">");
sb.append("<!ENTITY apos \"'\">");
sb.append("<!ENTITY lt \"<\">");
sb.append("<!ENTITY gt \">\">");
sb.append("<!ENTITY nbsp \" \">");
return sb.toString();
}
});
}
catch (Throwable e)
{
log.error("Error initializing cruxToHtmlTransformer.", e);
}
finally
{
lock.unlock();
}
}
}
}
}