org.milyn.delivery.ContentDeliveryConfigBuilder Maven / Gradle / Ivy
The newest version!
/*
Milyn - Copyright (C) 2006 - 2010
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License (version 2.1) as published by the Free Software
Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details:
http://www.gnu.org/licenses/lgpl.txt
*/
package org.milyn.delivery;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.milyn.SmooksException;
import org.milyn.StreamFilterType;
import org.milyn.cdr.*;
import org.milyn.container.ApplicationContext;
import org.milyn.delivery.dom.DOMContentDeliveryConfig;
import org.milyn.delivery.sax.SAXContentDeliveryConfig;
import org.milyn.dtd.DTDStore;
import org.milyn.dtd.DTDStore.DTDObjectContainer;
import org.milyn.event.types.ConfigBuilderEvent;
import org.milyn.profile.ProfileSet;
import java.io.ByteArrayInputStream;
import java.util.*;
import java.util.Map.Entry;
/**
* Content delivery configuration builder.
* @author tfennelly
*/
public class ContentDeliveryConfigBuilder {
/**
* Logger.
*/
private static Log logger = LogFactory.getLog(ContentDeliveryConfigBuilder.class);
/**
* Context key for the table of loaded ContentDeliveryConfig instances.
*/
private static final String DELIVERY_CONFIG_TABLE_CTX_KEY = ContentDeliveryConfig.class.getName() + "#configTable";
/**
* Profile set.
*/
private ProfileSet profileSet;
/**
* Container context.
*/
private ApplicationContext applicationContext;
/**
* XML selector content spec definition prefix
*/
private static final String ELCSPEC_PREFIX = "elcspec:";
/**
* An unsorted list of SmooksResourceConfiguration.
*/
private List resourceConfigsList = new ArrayList();
/**
* Table (by element) of sorted SmooksResourceConfiguration instances keyed by selector value. Each table entry
* contains a List of SmooksResourceConfiguration instances.
*/
private LinkedHashMap> resourceConfigTable = new LinkedHashMap>();
/**
* Visitor Config.
*/
private VisitorConfigMap visitorConfig;
/**
* Config builder events list.
*/
private List configBuilderEvents = new ArrayList();
/**
* DTD for the associated device.
*/
private DTDObjectContainer dtd;
/**
* Private (hidden) constructor.
* @param profileSet Profile set.
* @param applicationContext Container context.
*/
private ContentDeliveryConfigBuilder(ProfileSet profileSet, ApplicationContext applicationContext) {
this.profileSet = profileSet;
this.applicationContext = applicationContext;
visitorConfig = new VisitorConfigMap(applicationContext);
visitorConfig.setConfigBuilderEvents(configBuilderEvents);
}
/**
* Get the ContentDeliveryConfig instance for the specified profile set.
* @param profileSet The profile set with which this delivery config is associated.
* @param applicationContext Application context.
* @param extendedVisitorConfigMap Preconfigured/extended Visitor Configuration Map.
* @return The ContentDeliveryConfig instance for the named table.
*/
public static ContentDeliveryConfig getConfig(ProfileSet profileSet, ApplicationContext applicationContext, VisitorConfigMap extendedVisitorConfigMap) {
ContentDeliveryConfig config;
LinkedHashMap configTable;
if(profileSet == null) {
throw new IllegalArgumentException("null 'profileSet' arg passed in method call.");
} else if(applicationContext == null) {
throw new IllegalArgumentException("null 'applicationContext' arg passed in method call.");
}
// Get the delivery config config from container context.
configTable = getDeliveryConfigTable(applicationContext);
if(configTable == null) {
synchronized(ContentDeliveryConfigBuilder.class) {
// Try again, just in case we have 1+ threads firing...
configTable = getDeliveryConfigTable(applicationContext);
if(configTable == null) {
configTable = new LinkedHashMap();
applicationContext.setAttribute(DELIVERY_CONFIG_TABLE_CTX_KEY, configTable);
}
}
}
// Get the delivery config instance for the base profile...
config = configTable.get(profileSet.getBaseProfile());
if(config == null) {
synchronized(ContentDeliveryConfigBuilder.class) {
// Try again, just in case we have 1+ threads firing on the same profile...
config = configTable.get(profileSet.getBaseProfile());
if(config == null) {
ContentDeliveryConfigBuilder configBuilder = new ContentDeliveryConfigBuilder(profileSet, applicationContext);
configBuilder.load();
config = configBuilder.createConfig(extendedVisitorConfigMap);
configTable.put(profileSet.getBaseProfile(), config);
}
}
}
return config;
}
private ContentDeliveryConfig createConfig(VisitorConfigMap extendedVisitorConfigMap) {
boolean sortVisitors = ParameterAccessor.getBoolParameter(ContentDeliveryConfig.SMOOKS_VISITORS_SORT, true, resourceConfigTable);
StreamFilterType filterType;
visitorConfig.addAll(extendedVisitorConfigMap);
filterType = getStreamFilterType();
configBuilderEvents.add(new ConfigBuilderEvent("SAX/DOM support characteristics of the Resource Configuration map:\n" + getResourceFilterCharacteristics()));
configBuilderEvents.add(new ConfigBuilderEvent("Using Stream Filter Type: " + filterType));
if(filterType == StreamFilterType.DOM) {
DOMContentDeliveryConfig domConfig = new DOMContentDeliveryConfig();
logger.debug("Using the DOM Stream Filter.");
domConfig.setAssemblyVisitBefores(visitorConfig.getDomAssemblyVisitBefores());
domConfig.setAssemblyVisitAfters(visitorConfig.getDomAssemblyVisitAfters());
domConfig.setProcessingVisitBefores(visitorConfig.getDomProcessingVisitBefores());
domConfig.setProcessingVisitAfters(visitorConfig.getDomProcessingVisitAfters());
domConfig.setSerializationVisitors(visitorConfig.getDomSerializationVisitors());
domConfig.setVisitCleanables(visitorConfig.getVisitCleanables());
domConfig.setApplicationContext(applicationContext);
domConfig.setSmooksResourceConfigurations(resourceConfigTable);
domConfig.setDtd(dtd);
domConfig.getConfigBuilderEvents().addAll(configBuilderEvents);
if(sortVisitors) {
domConfig.sort();
}
domConfig.addToExecutionLifecycleSets();
domConfig.initializeXMLReaderPool();
domConfig.configureFilterBypass();
// Tell all interested listeners that the config builder for the profile has now been created.
fireEvent(ContentDeliveryConfigBuilderLifecycleEvent.CONFIG_BUILDER_CREATED);
return domConfig;
} else {
SAXContentDeliveryConfig saxConfig = new SAXContentDeliveryConfig();
logger.debug("Using the SAX Stream Filter.");
saxConfig.setVisitBefores(visitorConfig.getSaxVisitBefores());
saxConfig.setVisitAfters(visitorConfig.getSaxVisitAfters());
saxConfig.setVisitCleanables(visitorConfig.getVisitCleanables());
saxConfig.setApplicationContext(applicationContext);
saxConfig.setSmooksResourceConfigurations(resourceConfigTable);
saxConfig.setDtd(dtd);
saxConfig.getConfigBuilderEvents().addAll(configBuilderEvents);
saxConfig.optimizeConfig();
saxConfig.assertSelectorsNotAccessingText();
if(sortVisitors) {
saxConfig.sort();
}
saxConfig.addToExecutionLifecycleSets();
saxConfig.initializeXMLReaderPool();
saxConfig.addIndexCounters();
// Tell all interested listeners that the config builder for the profile has now been created.
fireEvent(ContentDeliveryConfigBuilderLifecycleEvent.CONFIG_BUILDER_CREATED);
return saxConfig;
}
}
private StreamFilterType getStreamFilterType() {
StreamFilterType filterType;
if(logger.isDebugEnabled()) {
logger.debug("SAX/DOM support characteristics of the Resource Configuration map:\n" + getResourceFilterCharacteristics());
}
String filterTypeParam = ParameterAccessor.getStringParameter(Filter.STREAM_FILTER_TYPE, resourceConfigTable);
if(visitorConfig.getSaxVisitorCount() == visitorConfig.getVisitorCount() && visitorConfig.getDomVisitorCount() == visitorConfig.getVisitorCount()) {
if(filterTypeParam == null) {
filterType = StreamFilterType.SAX;
logger.debug("All configured XML Element Content Handler resource configurations can be " +
"applied using the SAX or DOM Stream Filter. Defaulting to " + filterType + " Filter. Set '" + ParameterAccessor.GLOBAL_PARAMETERS + ":"
+ Filter.STREAM_FILTER_TYPE + "'.");
logger.debug("You can explicitly select the Filter type as follows:\n" +
"\t\t\n" +
"\t\t\tSAX/DOM\n" +
"\t\t ");
} else if(filterTypeParam.equalsIgnoreCase(StreamFilterType.DOM.name())) {
filterType = StreamFilterType.DOM;
} else if(filterTypeParam.equalsIgnoreCase(StreamFilterType.SAX.name())) {
filterType = StreamFilterType.SAX;
} else {
throw new SmooksException("Invalid '" + Filter.STREAM_FILTER_TYPE + "' configuration parameter value of '" + filterTypeParam + "'. Must be 'SAX' or 'DOM'.");
}
} else if(visitorConfig.getDomVisitorCount() == visitorConfig.getVisitorCount()) {
filterType = StreamFilterType.DOM;
} else if(visitorConfig.getSaxVisitorCount() == visitorConfig.getVisitorCount()) {
filterType = StreamFilterType.SAX;
} else {
throw new SmooksException("Ambiguous Resource Configuration set. All Element Content Handlers must support processing on the SAX and/or DOM Filter:\n" + getResourceFilterCharacteristics());
}
// If the filter type has been configured, we make sure the selected filter matches...
if(filterTypeParam != null) {
if(!filterTypeParam.equalsIgnoreCase(filterType.name())) {
throw new SmooksException("The configured Filter ('" + filterTypeParam + "') cannot be used with the specified set of Smooks visitors. The '" + filterType + "' Filter is the only filter that can be used for this set of Visitors. Turn on Debug logging for more information.");
}
}
return filterType;
}
/**
* Logging support function.
* @return Verbose characteristics string.
*/
private String getResourceFilterCharacteristics() {
StringBuffer stringBuf = new StringBuffer();
List printedHandlers = new ArrayList();
stringBuf.append("\t\tDOM SAX Resource ('x' equals supported)\n");
stringBuf.append("\t\t---------------------------------------------------------------------\n");
printHandlerCharacteristics(visitorConfig.getDomAssemblyVisitBefores(), stringBuf, printedHandlers);
printHandlerCharacteristics(visitorConfig.getDomAssemblyVisitAfters(), stringBuf, printedHandlers);
printHandlerCharacteristics(visitorConfig.getDomProcessingVisitBefores(), stringBuf, printedHandlers);
printHandlerCharacteristics(visitorConfig.getDomProcessingVisitAfters(), stringBuf, printedHandlers);
printHandlerCharacteristics(visitorConfig.getDomSerializationVisitors(), stringBuf, printedHandlers);
printHandlerCharacteristics(visitorConfig.getSaxVisitBefores(), stringBuf, printedHandlers);
printHandlerCharacteristics(visitorConfig.getSaxVisitAfters(), stringBuf, printedHandlers);
stringBuf.append("\n\n");
return stringBuf.toString();
}
private void printHandlerCharacteristics(ContentHandlerConfigMapTable table, StringBuffer stringBuf, List printedHandlers) {
Collection>> map = table.getTable().values();
for (List> mapList : map) {
for (ContentHandlerConfigMap configMap : mapList) {
ContentHandler handler = configMap.getContentHandler();
boolean domSupported = VisitorConfigMap.isDOMVisitor(handler);
boolean saxSupported = VisitorConfigMap.isSAXVisitor(handler);
if(printedHandlers.contains(handler)) {
continue;
} else {
printedHandlers.add(handler);
}
stringBuf.append("\t\t ")
.append(domSupported ? "x" : " ")
.append(" ")
.append(saxSupported ? "x" : " ")
.append(" ")
.append(configMap.getResourceConfig())
.append("\n");
}
}
}
@SuppressWarnings("unchecked")
private static LinkedHashMap getDeliveryConfigTable(ApplicationContext applicationContext) {
return (LinkedHashMap) applicationContext.getAttribute(DELIVERY_CONFIG_TABLE_CTX_KEY);
}
/**
* Build the ContentDeliveryConfigBuilder for the specified device.
*
* Creates the buildTable instance and populates it with the ProcessingUnit matrix
* for the specified device.
*/
private void load() {
resourceConfigsList.clear();
resourceConfigsList.addAll(Arrays.asList(applicationContext.getStore().getSmooksResourceConfigurations(profileSet)));
// Build and sort the resourceConfigTable table - non-transforming elements.
buildSmooksResourceConfigurationTable(resourceConfigsList);
sortSmooksResourceConfigurations(resourceConfigTable);
// If there's a DTD for this device, get it and add it to the DTDStore.
List dtdSmooksResourceConfigurations = resourceConfigTable.get("dtd");
if(dtdSmooksResourceConfigurations != null && dtdSmooksResourceConfigurations.size() > 0) {
SmooksResourceConfiguration dtdSmooksResourceConfiguration = (SmooksResourceConfiguration)dtdSmooksResourceConfigurations.get(0);
byte[] dtdDataBytes = dtdSmooksResourceConfiguration.getBytes();
if(dtdDataBytes != null) {
DTDStore.addDTD(profileSet, new ByteArrayInputStream(dtdDataBytes));
// Initialise the DTD reference for this config table.
dtd = DTDStore.getDTDObject(profileSet);
} else {
logger.error("DTD resource [" + dtdSmooksResourceConfiguration.getResource() + "] not found in classpath.");
}
}
// Expand the SmooksResourceConfiguration table and resort
expandSmooksResourceConfigurationTable();
sortSmooksResourceConfigurations(resourceConfigTable);
// Extract the ContentDeliveryUnits and build the tables
extractContentHandlers();
// Tell all interested listeners that all the handlers have now been created.
fireEvent(ContentDeliveryConfigBuilderLifecycleEvent.HANDLERS_CREATED);
if(logger.isDebugEnabled()) {
logResourceConfig();
}
}
/**
* Print a debug log of the resource configurations for the associated profile.
*/
private void logResourceConfig() {
logger.debug("==================================================================================================");
logger.debug("Resource configuration (sorted) for profile [" + profileSet.getBaseProfile() + "]. Sub Profiles: [" + profileSet + "]");
Iterator configurations = resourceConfigTable.entrySet().iterator();
int i = 0;
while(configurations.hasNext()) {
Map.Entry entry = (Entry) configurations.next();
List resources = (List)entry.getValue();
logger.debug(i + ") " + entry.getKey());
for (int ii = 0; ii < resources.size(); ii++) {
logger.debug("\t(" + ii + ") " + resources.get(ii));
}
}
logger.debug("==================================================================================================");
}
/**
* Build the basic SmooksResourceConfiguration table from the list.
* @param resourceConfigsList List of SmooksResourceConfigurations.
*/
private void buildSmooksResourceConfigurationTable(List resourceConfigsList) {
for (final Object resourceConfig : resourceConfigsList)
{
addResourceConfiguration((SmooksResourceConfiguration) resourceConfig);
}
}
/**
* Add the supplied resource configuration to this configuration's main
* resource configuration list.
* @param config The configuration to be added.
*/
private void addResourceConfiguration(SmooksResourceConfiguration config) {
String target = config.getSelector();
// If it's contextual, it's targeting an XML element...
if(config.isSelectorContextual()) {
target = config.getTargetElement();
}
addResourceConfiguration(target, config);
}
/**
* Add the config for the specified element.
* @param element The element to which the config is to be added.
* @param resourceConfiguration The Object to be added.
*/
@SuppressWarnings("unchecked")
private void addResourceConfiguration(String element, SmooksResourceConfiguration resourceConfiguration) {
// Add it to the unsorted list...
if(!resourceConfigsList.contains(resourceConfiguration)) {
resourceConfigsList.add(resourceConfiguration);
}
// Add it to the sorted resourceConfigTable...
List elementConfigList = resourceConfigTable.get(element);
if(elementConfigList == null) {
elementConfigList = new Vector();
resourceConfigTable.put(element, elementConfigList);
}
if(!elementConfigList.contains(resourceConfiguration)) {
elementConfigList.add(resourceConfiguration);
}
}
/**
* Expand the SmooksResourceConfiguration table.
*
* Expand the XmlDef entries to the target elements etc.
*/
private void expandSmooksResourceConfigurationTable() {
class ExpansionSmooksResourceConfigurationStrategy implements SmooksResourceConfigurationStrategy {
private ExpansionSmooksResourceConfigurationStrategy() {
}
public void applyStrategy(String elementName, SmooksResourceConfiguration resourceConfig) {
// Expand XmlDef entries.
if(resourceConfig.isXmlDef()) {
String[] elements = getDTDElements(resourceConfig.getSelector().substring(SmooksResourceConfiguration.XML_DEF_PREFIX.length()));
for (final String element : elements)
{
addResourceConfiguration(element, resourceConfig);
}
}
// Add code to expand other expandable entry types here.
}
}
SmooksResourceConfigurationTableIterator tableIterator = new SmooksResourceConfigurationTableIterator(new ExpansionSmooksResourceConfigurationStrategy());
tableIterator.iterate();
}
/**
* Iterate over the table smooks-resource instances and sort the SmooksResourceConfigurations
* on each element. Ordered by specificity.
*/
@SuppressWarnings("unchecked")
private void sortSmooksResourceConfigurations(Map> table) {
Parameter sortParam = ParameterAccessor.getParameter("sort.resources", table);
if(sortParam != null && sortParam.getValue().trim().equalsIgnoreCase("true")) {
if(!table.isEmpty()) {
for (final Object o : table.entrySet())
{
Entry entry = (Entry) o;
List markupElSmooksResourceConfigurations = (List) entry.getValue();
SmooksResourceConfiguration[] resourceConfigs = (SmooksResourceConfiguration[]) markupElSmooksResourceConfigurations
.toArray(new SmooksResourceConfiguration[0]);
SmooksResourceConfigurationSortComparator sortComparator = new SmooksResourceConfigurationSortComparator(profileSet);
Arrays.sort(resourceConfigs, sortComparator);
entry.setValue(new Vector(Arrays.asList(resourceConfigs)));
}
}
}
}
/**
* Extract the ContentHandler instances from the SmooksResourceConfiguration table and add them to
* their respective tables.
*/
private void extractContentHandlers() {
ContentHandlerExtractionStrategy cduStrategy = new ContentHandlerExtractionStrategy(applicationContext);
SmooksResourceConfigurationTableIterator tableIterator = new SmooksResourceConfigurationTableIterator(cduStrategy);
tableIterator.iterate();
}
/**
* Get the DTD elements for specific device context.
* @param string DTD spec string e.g. "elcspec:empty"
* @return List of element names.
*/
private String[] getDTDElements(String string) {
String tmpString = string;
if(tmpString.startsWith(ELCSPEC_PREFIX)) {
tmpString = tmpString.substring(ELCSPEC_PREFIX.length());
if(tmpString.equals("empty")) {
return dtd.getEmptyElements();
} else if(tmpString.equals("not-empty")) {
return dtd.getNonEmptyElements();
} else if(tmpString.equals("any")) {
return dtd.getAnyElements();
} else if(tmpString.equals("not-any")) {
return dtd.getNonAnyElements();
} else if(tmpString.equals("mixed")) {
return dtd.getMixedElements();
} else if(tmpString.equals("not-mixed")) {
return dtd.getNonMixedElements();
} else if(tmpString.equals("pcdata")) {
return dtd.getPCDataElements();
} else if(tmpString.equals("not-pcdata")) {
return dtd.getNonPCDataElements();
}
}
throw new IllegalStateException("Unsupported DTD spec definition [" + string + "]");
}
private void logExecutionEvent(SmooksResourceConfiguration resourceConfig, String message) {
configBuilderEvents.add(new ConfigBuilderEvent(resourceConfig, message));
}
private void fireEvent(ContentDeliveryConfigBuilderLifecycleEvent event) {
List