/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*
* University Of Edinburgh (EDINA)
* Scotland
*
*
* File Name : IMSHtmlPreviewGenerator.java
* Author : gwaller
* Approver : Gareth Waller
*
* Notes :
*
*
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* HISTORY
* -------
*
* $LastChangedRevision$
* $LastChangedDate$
* $LastChangedBy$
*/
package uk.ac.jorum.packager.preview;
import java.io.StringWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.dspace.content.Bitstream;
import org.dspace.content.BitstreamFormat;
import org.dspace.content.Bundle;
import org.dspace.content.DCValue;
import org.dspace.content.Item;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.jdom.Element;
import uk.ac.jorum.dspace.utils.BundleUtils;
import uk.ac.jorum.packager.IMSManifest;
import uk.ac.jorum.packager.XMLManifest;
import uk.ac.jorum.utils.ExceptionLogger;
/**
* @author gwaller
*
*/
@SuppressWarnings("deprecation")
public class IMSHtmlPreviewGenerator {
/** log4j logger */
private static Logger logger = Logger.getLogger(IMSHtmlPreviewGenerator.class);
public static final String VELOCITY_TEMPLATE_DIR = "velocity_templates/";
public static final String PREVIEW_LEFT_FRAME_TEMPLATE = VELOCITY_TEMPLATE_DIR + "IMSPackagePreview_left_frame.vm";
public static final String NODE_TEMPLATE = VELOCITY_TEMPLATE_DIR + "node.vm";
public static final String ITEM_TITLE = "itemTitle";
public static final String TREE_ROOT = "treeRoot";
public static final String URL_BITSTREAMS = "urlBitstreams";
public static final String BITSTREAM_HANDLE_URL = "bitStreamHandleUrl";
public static final String HANDLE_URL = "handleUrl";
public static final String NODE_VTL = "nodeVTL";
public static final String NODE = "node";
public static final String FIRST_BITSTREAM = "firstBitstream";
private static VelocityEngine ve;
static{
/*
* now create a new VelocityEngine instance, and
* configure it to use the category
*/
ve = new VelocityEngine();
ve.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
"org.apache.velocity.runtime.log.Log4JLogChute" );
ve.setProperty("runtime.log.logsystem.log4j.logger", IMSHtmlPreviewGenerator.class.getCanonicalName());
// Set a classpath loader
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "previewloader");
ve.setProperty("previewloader.resource.loader.class", ClasspathResourceLoader.class.getCanonicalName());
ve.setProperty("velocimacro.library", VELOCITY_TEMPLATE_DIR + "VM_global_library.vm");
try{
ve.init();
} catch (Exception e){
ExceptionLogger.logException(logger, e);
ve = null;
}
}
private static String getDspaceUrl() throws Exception{
// Is there a better way to get the Dspace URL - what if we have the JSP and XMLUI running??
String dspaceUrl = ConfigurationManager.getProperty("dspace.url");
if (dspaceUrl == null){
// Error - can't generate preview!
throw new Exception("Config property dspace.url is null");
}
return dspaceUrl;
}
private static String generateNodeTemplate(PreviewTreeNode node, String handleUrl) throws Exception{
String result = "";
VelocityContext context = new VelocityContext();
Template nodeTemplate = ve.getTemplate(NODE_TEMPLATE);
context.put(BITSTREAM_HANDLE_URL, handleUrl);
context.put(NODE, node);
if (node.hasChildren()){
String nodeVtl = "";
for (PreviewTreeNode child:node.getChildren()){
nodeVtl += "\n" + generateNodeTemplate(child, handleUrl);
}
context.put(NODE_VTL, nodeVtl);
}
StringWriter w = new StringWriter();
nodeTemplate.merge(context, w);
result = w.toString();
return result;
}
private static VelocityContext buildVelocityContext(Item item) throws Exception{
VelocityContext context = new VelocityContext();
String title = "";
DCValue[] titleValues = item.getDC("title", Item.ANY, Item.ANY);
if (titleValues.length > 0){
title = titleValues[0].value;
}
context.put(ITEM_TITLE, title);
String bitstreamHandleUrl = getDspaceUrl() + "/bitstream/handle/" + item.getHandle();
context.put(BITSTREAM_HANDLE_URL, bitstreamHandleUrl);
String handleUrl = getDspaceUrl() + "/handle/" + item.getHandle();
context.put(HANDLE_URL, handleUrl);
// Create root tree node
PreviewTreeNode rootNode = new PreviewTreeNode();
rootNode.setNodeName(title);
rootNode.setBitStream(null);
rootNode.setFirst(0);
/*
* We have a content package - need to read original manifest and inspect resources and organizations
*/
IMSManifest imsManifest = null;
try {
imsManifest = (IMSManifest) IMSManifest.readOriginalManifest(item);
/*
* Create a map of the resource element 'identifier' and 'href' values
*/
Map<String, String> resourceMap = new HashMap<String, String>();
for (Element resource : imsManifest.getResources()) {
String identifierAtt = resource.getAttribute(XMLManifest.IDENTIFIER_ATTR).getValue();
String hrefAtt = resource.getAttribute(XMLManifest.HREF_ATTR).getValue();
logger.debug("Get resource attributes: Iden_ref=" + identifierAtt + ", href=" + hrefAtt);
resourceMap.put(identifierAtt, hrefAtt);
}
/*
* Determine organization elements from from manifest and iterate through them
*/
// Retrieve 'organizations' element
Element organizationsElement = imsManifest.getManifestDocument().getRootElement().getChild(IMSManifest.ORGANIZATIONS_ELEM, IMSManifest.IMS_CP_NS);
// Retrieve all 'organization' elements for the manifest 'organization'
@SuppressWarnings("unchecked")
List<Element> organizationList = (List<Element>) organizationsElement.getChildren(IMSManifest.ORGANIZATION_ELEM, IMSManifest.IMS_CP_NS);
for (Iterator<Element> orgIterator = organizationList.iterator(); orgIterator.hasNext();)
{
Element organizationElement = orgIterator.next();
@SuppressWarnings("unchecked")
List<Element> itemsList = (List<Element>) organizationElement.getChildren(IMSManifest.ITEM_ELEM,IMSManifest.IMS_CP_NS);
for (Element itemElement : itemsList)
{
rootNode = createItemNode(rootNode, rootNode, itemElement, item, resourceMap, context);
}
}
} catch (Exception e) {
logger.error("Problem reading in original IMS manifest file.", e);
}
logger.debug("Contents of rootNode=" + rootNode);
context.put(TREE_ROOT, rootNode);
String vtl = generateNodeTemplate(rootNode, bitstreamHandleUrl);
context.put(NODE_VTL, vtl);
// populate urls array
ArrayList<Bitstream> urlBitstreams = new ArrayList<Bitstream>(20);
Bundle[] urlBundles = item.getBundles(Constants.URL_BUNDLE);
for (Bundle b : urlBundles){
Bitstream[] streams = b.getBitstreams();
for (Bitstream s:streams){
urlBitstreams.add(s);
}
}
context.put(URL_BITSTREAMS, urlBitstreams);
return context;
}
private static PreviewTreeNode createItemNode(PreviewTreeNode rootNode,
PreviewTreeNode parentNode,
Element itemElement,
Item item,
Map<String, String> resourceMap,
VelocityContext context){
// Check whether 'item' element has the 'isvisible' attribute
String isVisible = "false";
if (itemElement.getAttributeValue(IMSManifest.IS_VISIBLE_ATTR) == null)
{
// No 'isvisible' attribute set so default to 'true'
isVisible = "true";
} else {
isVisible = itemElement.getAttributeValue(IMSManifest.IS_VISIBLE_ATTR);
}
// When 'isvisible is 'true' we need to create a tree node
if (isVisible.equals("true"))
{
// Create new tree node for this 'item' and set it's name
PreviewTreeNode itemNode = new PreviewTreeNode();
if (itemElement.getChildText(IMSManifest.TITLE_ELEM, IMSManifest.IMS_CP_NS) != null)
{
// 'title' element exists for this 'item' so set the name of this tree node to the value of 'title' element
itemNode.setNodeName(itemElement.getChildText(IMSManifest.TITLE_ELEM, IMSManifest.IMS_CP_NS));
}
else
{
// No 'title' element exists for this 'item' so set the name of this tree node to the generic name 'Item'
itemNode.setNodeName("Item");
}
// Check whether 'item' has an 'identifierref' attribute and if so set the correct bitstream
if (itemElement.getAttribute(IMSManifest.IDENTIFIER_REF_ATTR) != null)
{
// Determine the current element's Bitstream name
String bsName = resourceMap.get(itemElement.getAttribute(IMSManifest.IDENTIFIER_REF_ATTR).getValue());
logger.debug("Current item element's Bitstream name: " + bsName );
// Determine Content Bundle Bitstreams
Bundle[] contentBundle = null;
try {
contentBundle = item.getBundles(Constants.CONTENT_BUNDLE_NAME);
Bitstream[] contentBitstreams = null;
for (Bundle bundle : contentBundle) {
contentBitstreams = bundle.getBitstreams();
// Iterate over bitstreams
for(Bitstream bs : contentBitstreams)
{
// If item element's Bitstream name matches the Bitstream name then set the node Bitstream
if (bs.getName().equals(bsName))
{
itemNode.setBitStream(bs);
int first = rootNode.getFirst();
if ( first == 0)
{
context.put(FIRST_BITSTREAM, bs);
first++;
rootNode.setFirst(first);
}
}
}
}
} catch (SQLException e) {
logger.error("Error: Problem content bundle - no point proceeding with this item.", e);
}
}
// Create 'item' elements that are children of this 'item' element
@SuppressWarnings("unchecked")
List<Element> childItemList = (List<Element>) itemElement.getChildren(IMSManifest.ITEM_ELEM,IMSManifest.IMS_CP_NS);
for (Element childItem : childItemList)
{
itemNode = createItemNode(rootNode, itemNode, childItem, item, resourceMap, context);
}
// Add this item node to it's parent node
parentNode.addChild(itemNode);
}
return parentNode;
}
public static Bitstream renderPage(Context c,
Item item,
Bundle previewBundle,
VelocityContext veContext,
String templateName,
String bitstreamName,
boolean markAsPrimary) throws Exception{
StringWriter w = new StringWriter();
Template t = ve.getTemplate(templateName);
t.merge(veContext, w );
// Now store the merged template in a bitstream
BitstreamFormat bs_format = BitstreamFormat.findByShortDescription(c, "HTML");
// GWaller 29/1/10 IssueID #170 - delete the preview stream if it already exists. We might be re-generating previews.
Bitstream previewStream = previewBundle.getBitstreamByName(bitstreamName);
if (previewStream != null){
previewBundle.removeBitstream(previewStream);
}
return BundleUtils.setBitstreamFromBytes(previewBundle, bitstreamName, bs_format, w.toString().getBytes(), markAsPrimary);
}
public static Bitstream renderLeftFrame(Context c, Item item, Bundle previewBundle, VelocityContext veContext) throws Exception{
return renderPage(c, item, previewBundle, veContext, PREVIEW_LEFT_FRAME_TEMPLATE, Constants.PREVIEW_PACKAGE_BITSTREAM, true);
}
@SuppressWarnings("unused")
public static void generatePreviewBitstream(Context c, Item item, Bundle previewBundle) throws Exception{
if (ve != null){
VelocityContext veContext = buildVelocityContext(item);
Bitstream leftFrame = renderLeftFrame(c, item, previewBundle, veContext);
} else {
throw new Exception("Velocity not initialised");
}
}
}