/*
* -------------------------------------------------------------------------
* Copyright 2014
* Centre for Information Modeling - Austrian Centre for Digital Humanities
*
* 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.emile.cirilo.ecm.templates;
import org.emile.cirilo.Common;
import org.emile.cirilo.ecm.exceptions.FedoraConnectionException;
import org.emile.cirilo.ecm.exceptions.FedoraIllegalContentException;
import org.emile.cirilo.ecm.exceptions.ObjectIsWrongTypeException;
import org.emile.cirilo.ecm.exceptions.ObjectNotFoundException;
import org.emile.cirilo.ecm.exceptions.PIDGeneratorException;
import org.emile.cirilo.ecm.repository.PidList;
import org.emile.cirilo.ecm.repository.Repository;
import org.emile.cirilo.ecm.utils.Constants;
import org.emile.cirilo.ecm.utils.XpathUtils;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import voodoosoft.jroots.dialog.CDefaultGuiAdapter;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactoryConfigurationException;
import org.apache.log4j.Logger;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.*;
import org.jdom.input.DOMBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
/**
* This class deals with the template subsystem.
*/
public class TemplateSubsystem {
private static Logger log = Logger.getLogger(TemplateSubsystem.class);
private static final String FOXML_DIGITAL_OBJECT_PID = "/foxml:digitalObject/@PID";
private static final String RELSEXT_ABOUT = "/foxml:digitalObject/foxml:datastream[@ID='RELS-EXT']/"
+ "foxml:datastreamVersion[position()=last()]/"
+ "foxml:xmlContent/rdf:RDF/"
+ "rdf:Description/@rdf:about";
private static final String DCIDENTIFIER = "/foxml:digitalObject/foxml:datastream[@ID='DC']/"
+ "foxml:datastreamVersion[position()=last()]/"
+ "foxml:xmlContent/oai_dc:dc/dc:identifier";
private static final String DCTITLE = "/foxml:digitalObject/foxml:datastream[@ID='DC']/"
+ "foxml:datastreamVersion[position()=last()]/"
+ "foxml:xmlContent/oai_dc:dc/dc:title";
private static final String ISTEMPLATEFOR = "/foxml:digitalObject/foxml:datastream[@ID='RELS-EXT']/"
+ "foxml:datastreamVersion[position()=last()]/"
+ "foxml:xmlContent/rdf:RDF/"
+ "rdf:Description/doms:isTemplateFor";
private static final String DATASTREAM_AUDIT = "/foxml:digitalObject/foxml:datastream[@ID='AUDIT']";
private static final String DATASTREAM_NEWEST = "/foxml:digitalObject/foxml:datastream/"
+ "foxml:datastreamVersion[position()=last()]";
private static final String DATASTREAM_CREATED = "/foxml:digitalObject/foxml:datastream/foxml:datastreamVersion";
private static final String OBJECTPROPERTY_CREATED = "/foxml:digitalObject/foxml:objectProperties/foxml:property[@NAME='info:fedora/fedora-system:def/model#createdDate']";
private static final String OBJECTPROPERTIES_LSTMODIFIED = "/foxml:digitalObject/foxml:objectProperties/foxml:property[@NAME='info:fedora/fedora-system:def/view#lastModifiedDate']";
/** AAR Ext 1.0 **/
private static final String DATASTREAM_CONTENTLOCATION = "/foxml:digitalObject/foxml:datastream/foxml:datastreamVersion/foxml:contentLocation/@REF";
private static final String DATASTREAM_GROUPR = "/foxml:digitalObject/foxml:datastream[@CONTROL_GROUP='R']/foxml:datastreamVersion/foxml:contentLocation/@REF";
private static final String OBJECTPROPERTIES_OWNERID = "/foxml:digitalObject/foxml:objectProperties/foxml:property[@NAME='info:fedora/fedora-system:def/model#ownerId']/@VALUE";
private static final String RELSEXT_DESCRIPTION = "/foxml:digitalObject/foxml:datastream[@ID='RELS-EXT']/"
+ "foxml:datastreamVersion[position()=last()]/foxml:xmlContent/rdf:RDF/rdf:Description[@rdf:about]";
private static final String DC_DATASTREAM = "/foxml:digitalObject/foxml:datastream[@ID='DC']/"
+ "foxml:datastreamVersion/"
+ "foxml:xmlContent/oai_dc:dc";
/**
* Mark the objpid object as a template for the cmpid object
* @param objpid the object to mark
* @param cmpid the content model to make objpid a template for
* @throws ObjectNotFoundException if either of the objects do not exist
* @throws FedoraConnectionException if anything went wrong in the communication
* @throws ObjectIsWrongTypeException if the object is not a data object or the content model is not a content model
*/
public void markObjectAsTemplate(
String objpid,
String cmpid)
throws ObjectNotFoundException, FedoraConnectionException,
ObjectIsWrongTypeException, FedoraIllegalContentException {
log.trace("Entering markObjectAsTemplate with params: " + objpid + " and "+cmpid );
//Working
if (!Repository.exist(cmpid)){
throw new ObjectNotFoundException("The content model '"+cmpid+
"' does not exist");
}
if (!Repository.exist(objpid)){
throw new ObjectNotFoundException("The data object '"+objpid+
"' does not exist");
}
boolean added = Repository.addRelation(objpid, Constants.TEMPLATE_REL, cmpid);
log.info("Marked object '"+objpid+"' as template for '"+cmpid+"'");
if (!added){
//The object is already a template. Note this in the log, and do no more
log.info("Object '"+objpid+"' was already a template for '"+cmpid+"' so no change was performed");
}
}
public PidList findTemplatesFor(String cmpid)
throws ObjectNotFoundException,
FedoraConnectionException,
FedoraIllegalContentException,
ObjectIsWrongTypeException {
//Working
log.trace("Entering findTemplatesFor with param '"+cmpid+"'");
if (Repository.exist(cmpid)){
if (Repository.isContentModel(cmpid)){
List<String> childcms
= Repository.getInheritingContentModels(cmpid);
String contentModel
= "<"+
Repository.ensureURI(cmpid)+
">\n";
String query = "select $object\n" +
"from <#ri>\n" +
"where\n" +
" $object <" + Constants.TEMPLATE_REL + "> " +
contentModel;
for (String childcm: childcms){
String cm = "<" +
Repository.ensureURI(childcm) +
">\n";
query = query +
"or $object <" +
Constants.TEMPLATE_REL +
"> " + cm;
}
return Repository.query(query);
} else {
throw new ObjectIsWrongTypeException("The pid '" +
cmpid +
"' is not a content model");
}
} else {
throw new ObjectNotFoundException("The pid '" +
cmpid +
"' is not in the repository");
}
}
public void makeTemplate(String templatepid, String ownerid, String newPid, String dctitle, String cm) {
try {
cloneInternalTemplate(templatepid, ownerid, newPid, (String) null, false);
if (!Common.exist(newPid.substring(1))) {
throw new Exception();
}
markObjectAsTemplate(newPid.substring(1), cm);
} catch (Exception e) {log.error(e.getLocalizedMessage(),e); }
return;
}
public String cloneTemplate(String templatepid, String ownerid, String newPid, String dctitle)
throws FedoraIllegalContentException,
FedoraConnectionException, PIDGeneratorException,
ObjectNotFoundException,
ObjectIsWrongTypeException, XPathFactoryConfigurationException {
return cloneInternalTemplate( templatepid, ownerid, newPid, dctitle, true);
}
public String cloneInternalTemplate(String templatepid, String ownerid, String newPid, String dctitle, boolean mode)
throws FedoraIllegalContentException,
FedoraConnectionException, PIDGeneratorException,
ObjectNotFoundException,
ObjectIsWrongTypeException, XPathFactoryConfigurationException {
//working
templatepid = Repository.ensurePID(templatepid);
log.debug("Entering cloneTemplate with param '" + templatepid + "'");
if (!Repository.exist(templatepid)){
throw new ObjectNotFoundException("The object (" + templatepid +
" does not exists");
}
if (!Repository.isTemplate(templatepid)){
throw new ObjectIsWrongTypeException("The pid (" + templatepid +
") is not a pid of a template");
}
// Get the document
Document document = Repository.getObjectXml(templatepid);
newPid = newPid + (!newPid.contains("context:") && !newPid.contains("query:") && !newPid.startsWith("container:") && !newPid.startsWith("$") ? "." + Repository.getNextPid().replaceAll("(.*):(.*)","$2") : "");
newPid = (newPid.startsWith("$") ? newPid.substring(1) : newPid);
log.debug("Generated new pid '" + newPid + "'");
try {
removeOlderVersions(document);
removeAudit(document);
log.debug("Audit removed");
removeDatastreamVersions(document);
log.debug("Datastreamsversions removed");
// Replace PID
replacePid(document, templatepid, newPid);
log.debug("Pids replaced");
/** AAR Ext 1.0 **/
replaceOwner(document, ownerid);
log.debug("Ownerid replaced");
removeDCidentifier(document);
log.debug("DC identifier removed");
if (dctitle != null) setExpathList(document, DCTITLE, dctitle );
removeCreated(document);
log.debug("CREATED removed");
removeLastModified(document);
log.debug("Last Modified removed");
removeTemplateRelation(document);
log.debug("Template relation removed");
} catch (XPathExpressionException e){
throw new FedoraIllegalContentException(
"Template object did not contain the correct structure",e);
}
//fix a fedora-bug
String xmlSource = "";
try {
DOMBuilder domBuilder = new DOMBuilder();
org.jdom.Document doc = domBuilder.build(document);
Format format = Format.getRawFormat();
format.setEncoding("UTF-8");
XMLOutputter outputter = new XMLOutputter(format);
xmlSource = outputter.outputString(doc).replace("<oai_dc:dc ", "<oai_dc:dc xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
} catch (Exception q) {}
//reingest the object
String stream = Repository.ingestDocument(
xmlSource,
"Cloned from template '" +
templatepid +
"' by user '" +
Repository.getUser() +
"'");
try {
if(!Common.exist(newPid)) {
throw new Exception();
}
if (newPid.contains("TEI")) {
setLocation(newPid, "STYLESHEET", "STYLESHEET", ownerid);
setLocation(newPid, "FO_STYLESHEET", "FO_STYLESHEET", ownerid);
setLocation(newPid, "DC_MAPPING", "DC_MAPPING", ownerid);
setLocation(newPid, "RDF_MAPPING", "RDF_MAPPING", ownerid);
setLocation(newPid, "REPLACEMENT_RULESET", "REPLACEMENT_RULESET", ownerid);
setLocation(newPid, "HSSF_STYLESHEET", "HSSF_STYLESHEET", ownerid);
}
if (newPid.contains("Context")) {
setLocation(newPid, "STYLESHEET", "CONTEXTtoHTML", ownerid);
setLocation(newPid, "FO_STYLESHEET", "CONTEXTtoFO", ownerid);
setLocation(newPid, "KML_TEMPLATE", "KML_TEMPLATE", ownerid);
setLocation(newPid, "HSSF_STYLESHEET", "HSSF_STYLESHEET", ownerid);
}
if (mode) {
byte[] buf = Repository.getDatastream(templatepid, "QUERY", (String) null);
Repository.modifyDatastream(newPid, "QUERY", "application/sparql-query", new String(buf).replaceAll("obj:self",newPid).getBytes("UTF-8"));
}
} catch (Exception e) {
log.error(e.getLocalizedMessage(),e);
}
return stream;
}
private void setLocation( String pid, String dsid, String ndsid, String ownerid) {
try {
byte[] buf = Repository.getDatastream(pid, dsid, (String) null);
String s = new String(buf);
int i = s.indexOf("cirilo:");
if (i > -1) {
Repository.modifyDatastream (pid, dsid, null, "R", s.substring(0,i)+"cirilo:"+ownerid+"/datastreams/"+ndsid+"/content");
}
} catch (Exception e) {
log.error(e.getLocalizedMessage(),e);
}
}
public String cloneTemplate(String templatepid, String ownerid, String newPid, CDefaultGuiAdapter moGA)
throws FedoraIllegalContentException,
FedoraConnectionException, PIDGeneratorException,
ObjectNotFoundException,
ObjectIsWrongTypeException, XPathFactoryConfigurationException {
//working
templatepid = Repository.ensurePID(templatepid);
log.trace("Entering cloneTemplate with param '" + templatepid + "'");
if (!Repository.exist(templatepid)){
throw new ObjectNotFoundException("The object (" + templatepid +
" does not exists");
}
if (!Repository.isTemplate(templatepid)){
throw new ObjectIsWrongTypeException("The pid (" + templatepid +
") is not a pid of a template");
}
//Get the document
Document document = Repository.getObjectXml(templatepid);
try {
newPid = newPid + (!newPid.contains("context:") && !newPid.contains("query:") && !newPid.startsWith("container:") ? "." + Repository.getNextPid().replaceAll("(.*):(.*)","$2") : "");
} catch (Exception ex) {
log.error(ex.getLocalizedMessage(),ex);
}
log.debug("Generated new pid '" + newPid + "'");
try {
removeOlderVersions(document);
removeAudit(document);
log.debug("Audit removed");
removeDatastreamVersions(document);
log.debug("Datastreamsversions removed");
// Replace PID
replacePid(document, templatepid, newPid);
log.debug("Pids replaced");
/** AAR Ext 1.0 **/
replaceOwner(document, ownerid);
log.debug("Ownerid replaced");
removeDCidentifier(document);
log.debug("DC identifier removed");
removeCreated(document);
log.debug("CREATED removed");
removeLastModified(document);
log.debug("Last Modified removed");
removeTemplateRelation(document);
log.debug("Template relation removed");
try {
JCheckBox jcbOAIProvider = (JCheckBox) moGA.getWidget("jcbOAIProvider");
if (jcbOAIProvider.isSelected()) {
addOAIItemID(document, newPid, RELSEXT_DESCRIPTION);
}
} catch (Exception ex) {
}
removeDCtitle(document);
log.debug("DC title removed");
addDCMetadata(document, DC_DATASTREAM, moGA);
} catch (XPathExpressionException e){
throw new FedoraIllegalContentException(
"Template object did not contain the correct structure",e);
}
//fix a fedora-bug
String xmlSource = "";
try {
DOMBuilder domBuilder = new DOMBuilder();
org.jdom.Document doc = domBuilder.build(document);
Format format = Format.getRawFormat();
format.setEncoding("UTF-8");
XMLOutputter outputter = new XMLOutputter(format);
xmlSource = outputter.outputString(doc).replace("<oai_dc:dc ", "<oai_dc:dc xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
} catch (Exception q) {
log.error(q.getLocalizedMessage(),q);
}
//reingest the object
String stream = Repository.ingestDocument(
xmlSource,
"Cloned from template '" +
templatepid +
"' by user '" +
Repository.getUser() +
"'");
try {
byte[] buf = Repository.getDatastream(templatepid, "QUERY", (String) null);
Repository.modifyDatastream(newPid, "QUERY", "application/sparql-query", new String(buf).replaceAll("obj:self",newPid).getBytes("UTF-8"));
} catch (Exception e) {
log.error(e.getLocalizedMessage(),e);
}
return stream;
}
/** Private helper method for cloneTemplate. In a document, replaces the
* mention of oldpid with newpid
* @param doc the document to work on
* @param oldpid the old pid
* @param newpid the new pid
* @throws FedoraIllegalContentException If there is a problem understanding
* the document
* @throws javax.xml.xpath.XPathExpressionException if there was
*/
private void replacePid(Document doc, String oldpid, String newpid)
throws FedoraIllegalContentException, XPathExpressionException, XPathFactoryConfigurationException {
log.trace("Entering replacepid");
substituteAttribute(doc, FOXML_DIGITAL_OBJECT_PID,
Repository.ensurePID(newpid));
//** AAR Ext 1.0 **/
replaceAttribute(doc,
oldpid, Repository.ensurePID(newpid));
substituteAttribute(doc, RELSEXT_ABOUT,Repository.ensureURI(newpid));
}
//** AAR Ext 1.0 **/
private void replaceOwner (Document doc, String ownerid)
throws FedoraIllegalContentException, XPathExpressionException, XPathFactoryConfigurationException {
substituteAttribute(doc, OBJECTPROPERTIES_OWNERID, ownerid);
}
/**
* Utility method for removing all nodes from a query. Does not work
* for attributes
* @param doc the object
* @param query the adress of the nodes
* @throws XPathExpressionException if a xpath expression did not evaluate
*/
private void removeExpathList(Document doc, String query)
throws XPathExpressionException, XPathFactoryConfigurationException {
NodeList nodes = XpathUtils.
xpathQuery(doc,
query);
for (int i=0;i<nodes.getLength();i++){
Node node = nodes.item(i);
node.getParentNode().removeChild(node);
}
}
private void removeOlderVersions(Document doc)
throws XPathExpressionException, XPathFactoryConfigurationException {
NodeList nodes = XpathUtils.
xpathQuery(doc,"/foxml:digitalObject/foxml:datastream");
for (int i=0;i<nodes.getLength();i++){
Node node = nodes.item(i);
NodeList children = XpathUtils.xpathQuery(node,"foxml:datastreamVersion");
for (int j=0;j<children.getLength();j++){
if (j+1<children.getLength()) {
Node child = children.item(j);
child.getParentNode().removeChild(child);
}
}
}
}
private void setExpathList(Document doc, String query, String text)
throws XPathExpressionException, XPathFactoryConfigurationException {
NodeList nodes = XpathUtils.
xpathQuery(doc,
query);
for (int i=0;i<nodes.getLength();i++){
Node node = nodes.item(i);
node.setTextContent(text);
}
}
/**
* Utility method for changing the value of an attribute
* @param doc the object
* @param query the location of the Attribute
* @param value the new value
* @throws XPathExpressionException if a xpath expression did not evaluate
*/
private void substituteAttribute(Document doc, String query, String value)
throws XPathExpressionException, XPathFactoryConfigurationException {
NodeList nodes = XpathUtils.
xpathQuery(doc,
query);
for (int i=0;i<nodes.getLength();i++){
Node node = nodes.item(i);
node.setNodeValue(value);
}
}
//** AAR Ext .0 **/
private void replaceAttribute(Document doc, String oldpid, String newpid)
throws XPathExpressionException, XPathFactoryConfigurationException {
NodeList nodes = XpathUtils.xpathQuery(doc, DATASTREAM_CONTENTLOCATION);
for (int i=0;i<nodes.getLength();i++){
Node node = nodes.item(i);
String s = node.getTextContent();
node.setNodeValue(s.replace(oldpid+"/",newpid+"/").replace("/repositories/"+oldpid,"/repositories/"+newpid));
}
NodeList nodeq = XpathUtils.xpathQuery(doc, DATASTREAM_GROUPR);
for (int i=0;i<nodeq.getLength();i++){
Node node = nodeq.item(i);
String s = node.getTextContent();
String[] p = oldpid.split(":");
if (p.length > 1) oldpid=p[0]+"%3A"+p[1];
String[] q = newpid.split(":");
if (q.length > 1) newpid=q[0]+"%3A"+q[1];
node.setNodeValue(s.replace(oldpid,newpid));
// node.setNodeValue(s.replace(oldpid+"/",newpid+"/").replace("/repositories/"+oldpid,"/repositories/"+newpid));
}
}
/**
* Utility method for removing an attribute
* @param doc the object
* @param query the adress of the node element
* @param attribute the name of the attribute
* @throws XPathExpressionException if a xpath expression did not evaluate
*/
private void removeAttribute(Document doc, String query, String attribute)
throws XPathExpressionException, XPathFactoryConfigurationException {
NodeList nodes;
nodes = XpathUtils.xpathQuery(
doc,
query);
for (int i=0;i<nodes.getLength();i++){
Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes();
if (attrs.getNamedItem(attribute) != null){
attrs.removeNamedItem(attribute);
}
}
}
/**
* Removes the DC identifier from the DC datastream
* @param doc the object
* @throws XPathExpressionException if a xpath expression did not evaluate
*/
private void removeDCidentifier(Document doc)
throws XPathExpressionException, XPathFactoryConfigurationException {
//Then remove the pid in dc identifier
removeExpathList(doc, DCIDENTIFIER);
}
private void removeDCtitle(Document doc)
throws XPathExpressionException, XPathFactoryConfigurationException {
//Then remove the pid in dc identifier
removeExpathList(doc, DCTITLE);
}
/**
* Removes all template relations
* @param doc the object
* @throws XPathExpressionException if a xpath expression did not evaluate
*/
private void removeTemplateRelation(Document doc) throws
XPathExpressionException, XPathFactoryConfigurationException {
// Remove template relation
//TODO Constant for template relation
removeExpathList(doc, ISTEMPLATEFOR);
}
/**
* Removes the AUDIT datastream
* @param doc the object
* @throws XPathExpressionException if a xpath expression did not evaluate
*/
private void removeAudit(Document doc) throws
XPathExpressionException, XPathFactoryConfigurationException {
removeExpathList(doc, DATASTREAM_AUDIT);
}
/**
* Removes all datastream versions, except the newest
* @param doc the object
* @throws XPathExpressionException if a xpath expression did not evaluate
*/
private void removeDatastreamVersions(Document doc) throws
XPathExpressionException, XPathFactoryConfigurationException {
NodeList relationNodes;
relationNodes = XpathUtils.xpathQuery(
doc, DATASTREAM_NEWEST);
Node node = relationNodes.item(0);
Node datastreamnode = node.getParentNode();
//Remove all of the datastream node children
while (datastreamnode.getFirstChild() != null) {
datastreamnode.removeChild(
datastreamnode.getFirstChild());
}
datastreamnode.appendChild(node);
}
/**
* Removes the CREATED attribute on datastreamVersion and the createdDate objectProperty
* @param doc the object
* @throws XPathExpressionException if a xpath expression did not evaluate
*/
private void removeCreated(Document doc) throws XPathExpressionException, XPathFactoryConfigurationException {
log.trace("Entering removeCreated");
removeAttribute(doc, DATASTREAM_CREATED,"CREATED");
removeExpathList(doc, OBJECTPROPERTY_CREATED);
}
/**
* Removes the lastModifiedDate objectDate
* @param doc the object
* @throws XPathExpressionException if a xpath expression did not evaluate
*/
private void removeLastModified(Document doc) throws
XPathExpressionException, XPathFactoryConfigurationException {
removeExpathList(doc, OBJECTPROPERTIES_LSTMODIFIED);
}
private void addOAIItemID(Document doc, String pid, String query)
throws XPathExpressionException, XPathFactoryConfigurationException {
NodeList nodes = XpathUtils.
xpathQuery(doc,
query);
for (int i=0;i<nodes.getLength();i++){
Node node = nodes.item(i);
Element item = doc.createElement("oai:itemID");
item.setAttribute("xmlns:oai", "http://www.openarchives.org/OAI/2.0/");
item.setTextContent(pid);
node.appendChild(item);
}
}
private void addDCMetadata( Document doc, String query, CDefaultGuiAdapter moGA )
throws XPathExpressionException , XPathFactoryConfigurationException{
NodeList nodes = XpathUtils.
xpathQuery(doc,
query);
Node node = nodes.item(0);
try {
for (int i = 0; i < org.emile.cirilo.Common.DCMI.length; i++)
{
StringTokenizer st = new StringTokenizer((String) moGA.getText("jtf" + org.emile.cirilo.Common.DCMI[i]), "~");
if (st.hasMoreTokens()) {
while (st.hasMoreTokens()) {
String s = st.nextToken();
Element item = doc.createElement("dc:"+ org.emile.cirilo.Common.DCMI[i].toLowerCase());
item.setTextContent(s);
node.appendChild(item);
}
}
}
} catch (Exception e) {
}
}
}