/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You 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 com.esri.gpt.catalog.schema; import java.io.IOException; import java.io.StringWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.UUID; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.esri.gpt.catalog.arcims.GetDocumentRequest; import com.esri.gpt.catalog.arcims.ImsServiceException; import com.esri.gpt.catalog.management.MmdEnums; import com.esri.gpt.catalog.publication.PublicationRecord; import com.esri.gpt.catalog.publication.PublicationRequest; import com.esri.gpt.framework.context.RequestContext; import com.esri.gpt.framework.jsf.MessageBroker; import com.esri.gpt.framework.security.codec.Base64; import com.esri.gpt.framework.security.principal.Publisher; import com.esri.gpt.framework.util.Val; import com.esri.gpt.framework.xml.DomUtil; import com.esri.gpt.framework.xml.XmlIoUtil; import com.esri.gpt.framework.xml.XsltTemplate; import com.esri.gpt.framework.xml.XsltTemplates; /** * Provides functionality to connect a Schema with a metadata document. */ public class MetadataDocument { // class variables ============================================================= private static XsltTemplates XSLTTEMPLATES = new XsltTemplates(); // instance variables ========================================================== private String _enclosedXml = ""; private String _transformedToKnownXml = ""; private String _wrappingEsriDocId = ""; private String _sourceUri = ""; // constructors ================================================================ /** Default constructor. */ public MetadataDocument() {} // properties ================================================================== /** * Gets the configured schemas. * @param requestContext the request context * @return the configured schemas */ private Schemas getConfiguredSchemas(RequestContext requestContext) { return requestContext.getCatalogConfiguration().getConfiguredSchemas(); } // methods ===================================================================== /** * Loads, interrogates and evaluates the schema associated with an * XML string. * @param context the active request context * @param xml the document XML string * @param checkForEnclosure if true, check the XML for an enclosure * @return the evaluated and validated schema * @throws SchemaException if a schema related exception occurs */ private Schema evaluateSchema(RequestContext context, String xml, boolean checkForEnclosure) throws SchemaException { Schema schema = null; _transformedToKnownXml = ""; try { // load and interrogate the document Document dom = loadDom(xml,checkForEnclosure); Schemas schemas = getConfiguredSchemas(context); schema = schemas.interrogate(dom); if ((_enclosedXml != null) && (_enclosedXml.length() > 0)) { schema.setActiveDocumentXml(_enclosedXml); } else { schema.setActiveDocumentXml(xml); } // transform to a known schema if required String toKnownSchemaXslt = schema.getInterrogation().getToKnownSchemaXslt(); if ((toKnownSchemaXslt != null) && (toKnownSchemaXslt.length() > 0)) { try { XsltTemplate template = getCompiledTemplate(toKnownSchemaXslt); DOMSource source = new DOMSource(dom); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); HashMap<String,String> params = new HashMap<String,String>(); params.put("currentDate", getDateTime()); params.put("sourceUrlUuid", "{" + UUID.nameUUIDFromBytes( this._sourceUri.toLowerCase().toString().getBytes()) .toString().toUpperCase() + "}"); params.put("sourceUrl", this._sourceUri.toLowerCase().contains("?f=json")?this._sourceUri.substring(0,this._sourceUri.indexOf("?")):this._sourceUri); if(this._sourceUri != null){ String[] s = this._sourceUri.split("&"); for(int i=0;i < s.length; i++){ int serviceIndexStart = s[i].toUpperCase().indexOf("SERVICE="); if( serviceIndexStart > -1){ params.put("serviceType", s[i].substring(serviceIndexStart+8,s[i].length())); } } } template.transform(source,result,params); _transformedToKnownXml = Val.chkStr(writer.toString()); dom = loadDom(_transformedToKnownXml); schema = schemas.interrogate(dom); schema.setActiveDocumentXml(_transformedToKnownXml); } catch (TransformerException e) { throw new SchemaException("Unable to transform document with: "+toKnownSchemaXslt,e); } } // evaluate schema.evaluate(dom); } catch (XPathExpressionException e) { throw new SchemaException("Invalid schema XPath expression.",e); } return schema; } private String getDateTime() { DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd"); Date date = new Date(); return dateFormat.format(date); } /** * Gets a compiled XSLT template. * @param xsltPath the path to an XSLT * @return the compiled template * @throws IOException if an IO exception occurs * @throws TransformerException if a transformation exception occurs * @throws SAXException if a SAX parsing exception occurs */ private synchronized XsltTemplate getCompiledTemplate(String xsltPath) throws TransformerException { String sKey = xsltPath; XsltTemplate template = XSLTTEMPLATES.get(sKey); if (template == null) { template = XsltTemplate.makeTemplate(xsltPath); XSLTTEMPLATES.put(sKey,template); } return template; } /** * Loads an XML string into an XML Document. * @param xml the document XML string * @return the document * @throws SchemaException if the document fails to load */ private Document loadDom(String xml) throws SchemaException { try { Document dom = DomUtil.makeDomFromString(xml,true); return dom; } catch (ParserConfigurationException e) { throw new SchemaException("Unable to parse document.",e); } catch (SAXException e) { throw new SchemaException("Unable to parse document.",e); } catch (IOException e) { throw new SchemaException("Unable to parse document.",e); } } /** * Loads an XML string into an XML Document. * @param xml the document XML string * @param checkForEnclosure if true, check the XML for an enclosure * @return the document * @throws SchemaException if the document fails to load */ private Document loadDom(String xml, boolean checkForEnclosure) throws SchemaException { Document dom = loadDom(xml); if (checkForEnclosure) { String sEnclosedXml = lookForEnclosure(dom); if (sEnclosedXml.length() > 0) { dom = loadDom(sEnclosedXml); } } return dom; } /** * Looks for an enclosed XML document. * @param dom the document to check * @return the enclosed document's XML string (empty if no enclosure exists) */ private String lookForEnclosure(Document dom) { _enclosedXml = ""; _wrappingEsriDocId = ""; String sEnclosedXml = ""; try { XPath xpath = XPathFactory.newInstance().newXPath(); String sPath = "/metadata/Binary/Enclosure/Data[@SourceMetadata='yes']"; String sAgsMmdPath = "/metadata/Esri/ArcGISFormat"; String sAgsMmd = xpath.evaluate(sAgsMmdPath,dom); String sEncoded = xpath.evaluate(sPath,dom); if ((sEncoded != null) && (sEncoded.length() > 0) && (sAgsMmd == null)) { String sDecoded = Val.chkStr(Base64.decode(sEncoded,null)); sEnclosedXml = sDecoded; _wrappingEsriDocId = Val.chkStr(xpath.evaluate("/metadata/Esri/PublishedDocID",dom)); } } catch (XPathExpressionException e) { // should never be thrown } catch (IOException e) { // should never be thrown } _enclosedXml = sEnclosedXml; return sEnclosedXml; } /** * Prepares a schema for creation by the metadata editor. * @param context the active request context * @param schemaKey the key associated with the schema to create * @return the new schema * @throws SchemaException if a schema related exception occurs */ public Schema prepareForCreate(RequestContext context, String schemaKey) throws SchemaException { Schemas schemas = getConfiguredSchemas(context); return schemas.locate(schemaKey); } /** * Prepares a document for download by a publisher. * @param context the active request context * @param publisher the active publisher * @param uuid the document UUID * @return the document XML * @throws ImsServiceException if an ArcIMS communication exception occurs * @throws SchemaException if a schema related exception occurs */ public String prepareForDownload(RequestContext context, Publisher publisher, String uuid) throws ImsServiceException, SchemaException { try { GetDocumentRequest imsRequest = new GetDocumentRequest(context,publisher); imsRequest.executeGet(uuid); String sXml = imsRequest.getXml(); Document dom = loadDom(sXml); String sEnclosedXml = lookForEnclosure(dom); if (sEnclosedXml.length() > 0) { return sEnclosedXml; } else { return sXml; } } catch (TransformerException e) { throw new SchemaException("Unable to transform document.",e); } } /** * Prepares a document for editing. * @param context the active request context * @param publisher the active publisher * @param uuid the document UUID * @return the evaluated schema * @throws ImsServiceException if an ArcIMS communication exception occurs * @throws SchemaException if a schema related exception occurs */ public Schema prepareForEdit(RequestContext context, Publisher publisher, String uuid) throws ImsServiceException, SchemaException { try { GetDocumentRequest imsRequest = new GetDocumentRequest(context,publisher); imsRequest.executeGet(uuid); String sXml = imsRequest.getXml(); return evaluateSchema(context,sXml,true); } catch (TransformerException e) { throw new SchemaException("Unable to transform document.",e); } } /** * Prepares a document for publication. * <br/>The schema is loaded, interrogated, evaluated and validated. * @param context request context * @param record publication record * @return the evaluated and validated schema * @throws SchemaException if a schema related exception occurs */ public Schema prepareForPublication(RequestContext context, PublicationRecord record) throws SchemaException { String sXml = record.getSourceXml(); String status = record.getApprovalStatus(); boolean isDraft = status.equalsIgnoreCase(MmdEnums.ApprovalStatus.draft.toString()); this._sourceUri = record.getSourceFileName(); // evaluate Schema schema = evaluateSchema(context,sXml,true); if ((_enclosedXml != null) && (_enclosedXml.length() > 0)) { sXml = _enclosedXml; record.setSourceXml(sXml); } if ((_transformedToKnownXml != null) && (_transformedToKnownXml.length() > 0)) { sXml = _transformedToKnownXml; record.setSourceXml(sXml); } // validate (don't fully validate a draft) if (!isDraft) { schema.validate(); if (schema.getXsdLocation().length() > 0) { XsdValidator xsdv = new XsdValidator(); xsdv.validate(schema,sXml); } if (schema.getSchematronXslt().length() > 0) { SchematronValidator sv = new SchematronValidator(); sv.validate(schema,sXml); } } else { schema.ensureMinimals(); } return schema; } /** * Prepares a document for publication. * <br/>The schema is loaded, interrogated, evaluated and validated. * @param request publication request * @return the evaluated and validated schema * @throws SchemaException if a schema related exception occurs */ public Schema prepareForPublication(PublicationRequest request) throws SchemaException { return this.prepareForPublication(request.getRequestContext(), request.getPublicationRecord()); } /** * Prepares a document for viewing. * @param context the active request context * @param xml the document xml * @return the evaluated schema * @throws SchemaException if a schema related exception occurs */ public Schema prepareForView(RequestContext context, String xml) throws SchemaException { Schema schema = evaluateSchema(context,xml,true); schema.ensureMinimals(); return schema; } /** * Prepare xml for full viewing. * @param xml the xml to be prepared * @return the xml string * @throws SchemaException the schema exception */ public String prepareForFullViewing(String xml) throws SchemaException { try{ Document dom = loadDom(xml); String sEnclosedXml = lookForEnclosure(dom); if (sEnclosedXml.length() > 0) { return sEnclosedXml; } else { return xml; } } catch (SchemaException e) { throw new SchemaException("Unable to transform document.",e); } } /** * Executes a transformation. * @param xml the document XML * @param xsltPath the path to the XSLT * @return the transformed document * @throws TransformerException if an exception occurs */ public String transform(String xml, String xsltPath) throws TransformerException { XsltTemplate template = this.getCompiledTemplate(xsltPath); String result = template.transform(xml); return result; } /** * Transforms an metadata document XML to an HTML fragment suitable for display within the * Metadata Details page. * @param xml the document xml to transform * @param xsltPath the path to the details XSLT for the schema * @return the HTML fragment * @throws TransformerException * @deprecated Instead use transformDetails(String xml, String xsltPath,Locale locale) */ public String transformDetails(String xml, String xsltPath) throws TransformerException { XsltTemplate template = this.getCompiledTemplate(xsltPath); return template.transform(xml); } /** * Transforms an metadata document XML to an HTML fragment suitable for display within the * Metadata Details page. * @param xml the document xml to transform * @param detailsXslPath the path to the details XSLT for the schema * @param mb the message broker * @return the HTML fragment * @throws TransformerException * @throws IOException * @throws SAXException * @throws ParserConfigurationException * @throws XPathExpressionException */ public String transformDetails(String xml, String detailsXslPath,MessageBroker mb) throws TransformerException, IOException, ParserConfigurationException, SAXException, XPathExpressionException { Document detailsXml = DomUtil.makeDomFromResourcePath(detailsXslPath,true); Namespaces namespaces = new Namespaces(); namespaces.add("xsl","http://www.w3.org/1999/XSL/Transform"); NamespaceContextImpl ns = new NamespaceContextImpl(namespaces); XPath xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(ns); Node root = (Node) xpath.evaluate("/xsl:stylesheet", detailsXml, XPathConstants.NODE); if(root != null){ NodeList nlTemplates = (NodeList) xpath.evaluate("//xsl:template", root, XPathConstants.NODESET); if(nlTemplates != null){ for (int j = 0; j < nlTemplates.getLength(); j++) { Node ndTemplate = nlTemplates.item(j); if (ndTemplate != null) { NodeList nlWhen = (NodeList) xpath.evaluate("//xsl:when", ndTemplate, XPathConstants.NODESET); if (nlWhen != null) { for (int i = 0; i < nlWhen.getLength(); i++) { Node ndWhen = nlWhen.item(i); String key = Val.chkStr(ndWhen.getTextContent()); if(key.startsWith("i18n.catalog.")){ String value = mb.retrieveMessage(key.replace("i18n.", "")); ndWhen.setTextContent(value); } } } } } } } XsltTemplate xsl = new XsltTemplate(); String result = xsl.transform(XmlIoUtil.domToString(detailsXml), xml, null); return result; } }