/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.xmlui.wing;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.util.HashUtil;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.source.impl.validity.NOPValidity;
import org.dspace.app.xmlui.wing.element.PageMeta;
import org.dspace.core.ConfigurationManager;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Include metadata in the resulting DRI document as derived from the sitemap
* parameters.
*
* Parameters should consist of a dublin core name and value. The format for
* a parameter name must follow the form: "<element>.<qualifier>.<language>#order"
* The qualifier, language, and order are all optional components. The order
* component is an integer and is needed to ensure that parameter names are
* unique. Since Cocoon's parameters are Hashes duplicate names are not allowed
* the order syntax allows the sitemap programer to specify an order in which
* these metadata values should be placed inside the document.
*
* The following are a valid examples:
*
* <map:parameter name="theme.name.en" value="My Theme"/>
*
* <map:parameter name="theme.path" value="/MyTheme/"/>
*
* <map:parameter name="theme.css#1" value="style.css"/>
*
* <map:parameter name="theme.css#2" value="style.css-ie"/>
*
* <map:parameter name="theme.css#2" value="style.css-ff"/>
*
* @author Scott Phillips
* @author Roel Van Reeth (roel at atmire dot com)
* @author Art Lowel (art dot lowel at atmire dot com)
* @author Ben Bosman (ben at atmire dot com)
*/
public class IncludePageMeta extends AbstractWingTransformer implements CacheableProcessingComponent
{
/** The metadata loaded from the sitemap parameters. */
private List<Metadata> metadataList;
/**
* Generate the unique key.
* This key must be unique inside the space of this component.
*
* @return The generated key hashes the src
*/
public Serializable getKey()
{
String key = "";
for (Metadata metadata : metadataList)
{
key = "-" + metadata.getName() + "=" + metadata.getValue();
}
return HashUtil.hash(key);
}
/**
* Generate the validity object.
*
* @return The generated validity object or <code>null</code> if the
* component is currently not cacheable.
*/
public SourceValidity getValidity()
{
return NOPValidity.SHARED_INSTANCE;
}
/**
* Extract the metadata name value pairs from the sitemap parameters.
*/
public void setup(SourceResolver resolver, Map objectModel, String src,
Parameters parameters) throws ProcessingException, SAXException,
IOException
{
try
{
String[] names = parameters.getNames();
metadataList = new ArrayList<Metadata>();
for (String name : names)
{
String[] nameParts = name.split("#");
String dcName = null;
int order = -1;
if (nameParts.length == 1)
{
dcName = nameParts[0];
order = 1;
}
else if (nameParts.length == 2)
{
dcName = nameParts[0];
order = Integer.valueOf(nameParts[1]);
}
else
{
throw new ProcessingException("Unable to parse page metadata name, '" + name + "', into parts.");
}
String[] dcParts = dcName.split("\\.");
String element = null;
String qualifier = null;
String language = null;
if (dcParts.length == 1)
{
element = dcParts[0];
}
else if (dcParts.length == 2)
{
element = dcParts[0];
qualifier = dcParts[1];
}
else if (dcParts.length == 3)
{
element = dcParts[0];
qualifier = dcParts[1];
language = dcParts[2];
}
else
{
throw new ProcessingException("Unable to parse page metadata name, '" + name + "', into parts.");
}
String value = parameters.getParameter(name);
Metadata metadata = new Metadata(element,qualifier,language,order,value);
metadataList.add(metadata);
}
Collections.sort(metadataList);
}
catch (ParameterException pe)
{
throw new ProcessingException(pe);
}
// Initialize the Wing framework.
try
{
this.setupWing();
}
catch (WingException we)
{
throw new ProcessingException(we);
}
// concatenation
if (ConfigurationManager.getBooleanProperty("xmlui.theme.enableConcatenation",false)) {
metadataList = enableConcatenation();
}
}
/**
* Alters the URL to CSS, JS or JSON files to concatenate them.
* Enable the ConcatenationReader in the theme sitemap for
* concatenation to work correctly
*/
private List<Metadata> enableConcatenation() {
Metadata last = null;
List<Metadata> newMetadataList = new ArrayList<Metadata>();
for (Metadata metadata : metadataList)
{
// only try to concatenate css and js
String curfile = metadata.getValue();
if (curfile.lastIndexOf('?') != -1) {
curfile = curfile.substring(0, curfile.lastIndexOf('?'));
}
if (curfile.endsWith(".css") || curfile.endsWith(".js") || curfile.endsWith(".json")) {
String curval = metadata.getValue();
// check if this metadata and the last one are compatible
if(last != null && checkConcatenateMerge(last, metadata)) {
// merge
String lastval = last.getValue();
curval = metadata.getValue();
String newval = lastval.substring(0,lastval.lastIndexOf('.')) + ",";
newval += curval.substring(curval.lastIndexOf('/')+1,curval.lastIndexOf('.'));
newval += lastval.substring(lastval.lastIndexOf('.'));
last.value = newval;
} else {
// no merge, so add to list
newMetadataList.add(metadata);
// handle query string cases
if(curval.lastIndexOf('?') != -1) {
if(curval.substring(curval.lastIndexOf('?')).equals("?nominify")) {
// concat should still be possible, so set last
last = metadata;
} else if(curval.substring(curval.lastIndexOf('?')).equals("?noconcat")) {
// no concat should be possible so set last to null
last = null;
// query string can be removed
curval = curval.substring(0, curval.lastIndexOf('?'));
metadata.value = curval;
} else {
// no concat should be possible so set last to null
last = null;
// query string should be set to "nominify"
curval = curval.substring(0, curval.lastIndexOf('?')) + "?nominify";
metadata.value = curval;
}
} else {
// multiple possibilities:
// * last == null, so set it
// * no merge is possible, so change last to this metadata
// no query string, so concat and merge should be possible later on
last = metadata;
}
}
} else {
// wrong extension
newMetadataList.add(metadata);
}
}
return newMetadataList;
}
private boolean checkConcatenateMerge(Metadata last, Metadata current) {
// check if elements are equal
if(last.getElement() == null) {
if(current.getElement() != null) {
return false;
}
} else if(!last.getElement().equals(current.getElement())) {
return false;
}
// check if qualifiers are equal
if(last.getQualifier() == null) {
if(current.getQualifier() != null) {
return false;
}
} else if(!last.getQualifier().equals(current.getQualifier())) {
return false;
}
// check if languages are equal
if(last.getLanguage() == null) {
if(current.getLanguage() != null) {
return false;
}
} else if(!last.getLanguage().equals(current.getLanguage())) {
return false;
}
String curval = current.getValue();
String lastval = last.getValue();
// check if extensions and query strings are equal
if(!lastval.substring(lastval.lastIndexOf('.')).equals(curval.substring(curval.lastIndexOf('.')))) {
return false;
}
// check if paths are equal
if(!lastval.substring(0,lastval.lastIndexOf('/')+1).equals(curval.substring(0,curval.lastIndexOf('/')+1))) {
return false;
}
// only valid nonempty query string is "nominify"
if(curval.lastIndexOf('?') != -1
&& !"?nominify".equals(curval.substring(curval.lastIndexOf('?')))) {
return false;
}
return true;
}
/**
* Include the metadata in the page metadata.
*/
public void addPageMeta(PageMeta pageMeta) throws WingException
{
for (Metadata metadata : metadataList)
{
String element = metadata.getElement();
String qualifier = metadata.getQualifier();
String language = metadata.getLanguage();
String value = metadata.getValue();
// Add our new metadata.
pageMeta.addMetadata(element, qualifier, language)
.addContent(value);
}
}
/**
* Private class to keep track of metadata name/value pairs.
*/
static class Metadata implements Comparable<Metadata> {
private String element;
private String qualifier;
private String language;
private int order;
private String value;
public Metadata(String element,String qualifier, String language, int order, String value)
{
this.element = element;
this.qualifier = qualifier;
this.language = language;
this.order = order;
this.value = value;
}
public String getElement()
{
return this.element;
}
public String getQualifier()
{
return this.qualifier;
}
public String getLanguage()
{
return this.language;
}
public int getOrder()
{
return this.order;
}
public String getName()
{
String name = this.element;
if (this.qualifier != null)
{
name += "." + this.qualifier;
if (this.language != null)
{
name += "." + this.language;
}
}
name += "#" + order;
return name;
}
public String getValue()
{
return this.value;
}
public int compareTo(Metadata other)
{
String myName = this.element + "." +this.qualifier + "." + this.language;
String otherName = other.element + "." + other.qualifier + "." + other.language;
int result = myName.compareTo(otherName);
if (result == 0)
{
if (this.order == other.order )
{
result = 0; // These two metadata element's names are completely identical.
}
else if (this.order > other.order)
{
result = 1; // The other metadata element belongs AFTER this element.
}
else
{
result = -1; // The other metadata element belongs BEFORE this element.
}
}
return result;
}
}
}