/*
* eXist Open Source Native XML Database
* Copyright (C) 2009 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id$
*/
package org.exist.xquery.functions.validation;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.transform.stream.StreamSource;
import org.apache.log4j.Logger;
import org.exist.dom.NodeProxy;
import org.exist.memtree.DocumentImpl;
import org.exist.memtree.MemTreeBuilder;
import org.exist.memtree.NodeImpl;
import org.exist.storage.serializers.Serializer;
import org.exist.validation.ValidationReport;
import org.exist.validation.ValidationReportItem;
import org.exist.validation.internal.node.NodeInputStream;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.Base64Binary;
import org.exist.xquery.value.Base64BinaryDocument;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.JavaObjectValue;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceIterator;
import org.exist.xquery.value.Type;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.AttributesImpl;
/**
* Shared methods for validation functions.
*
* @author dizzzz
*/
public class Shared {
private final static Logger LOG = Logger.getLogger(Shared.class);
public final static String simplereportText = "true() if the " +
"document is valid and no single problem occured, false() for " +
"all other conditions. For detailed validation information " +
"use the corresponding -report() function.";
public final static String xmlreportText = "a validation report.";
/**
* Get input stream for specified resource.
*/
public static InputStream getInputStream(Item s, XQueryContext context) throws XPathException, MalformedURLException, IOException {
StreamSource streamSource = getStreamSource(s, context);
return streamSource.getInputStream();
}
/**
* Get stream source for specified resource, containing InputStream and
* location. Used by @see Jaxv.
*/
public static StreamSource[] getStreamSource(Sequence s, XQueryContext context) throws XPathException, MalformedURLException, IOException {
ArrayList<StreamSource> sources = new ArrayList<StreamSource>();
SequenceIterator i = s.iterate();
while (i.hasNext()) {
Item next = i.nextItem();
StreamSource streamsource = getStreamSource(next, context);
sources.add(streamsource);
}
StreamSource returnSources[] = new StreamSource[sources.size()];
returnSources = sources.toArray(returnSources);
return returnSources;
}
public static StreamSource getStreamSource(Item item, XQueryContext context) throws XPathException, MalformedURLException, IOException {
StreamSource streamSource = new StreamSource();
if (item.getType() == Type.JAVA_OBJECT) {
LOG.debug("Streaming Java object");
Object obj = ((JavaObjectValue) item).getObject();
if (!(obj instanceof File)) {
throw new XPathException("Passed java object should be a File");
}
File inputFile = (File) obj;
InputStream is = new FileInputStream(inputFile);
streamSource.setInputStream(is);
streamSource.setSystemId(inputFile.toURI().toURL().toString());
} else if (item.getType() == Type.ANY_URI) {
LOG.debug("Streaming xs:anyURI");
// anyURI provided
String url = item.getStringValue();
// Fix URL
if (url.startsWith("/")) {
url = "xmldb:exist://" + url;
}
InputStream is = new URL(url).openStream();
streamSource.setInputStream(is);
streamSource.setSystemId(url);
} else if (item.getType() == Type.ELEMENT || item.getType() == Type.DOCUMENT) {
LOG.debug("Streaming element or document node");
if (item instanceof NodeProxy) {
NodeProxy np = (NodeProxy) item;
String url = "xmldb:exist://" + np.getDocument().getBaseURI();
LOG.debug("Document detected, adding URL " + url);
streamSource.setSystemId(url);
}
// Node provided
Serializer serializer = context.getBroker().newSerializer();
NodeValue node = (NodeValue) item;
InputStream is = new NodeInputStream(serializer, node);
streamSource.setInputStream(is);
} else if (item.getType() == Type.BASE64_BINARY) {
LOG.debug("Streaming base64 binary");
Base64Binary base64 = (Base64Binary) item;
byte[] data = (byte[]) base64.toJavaObject(byte[].class);
InputStream is = new ByteArrayInputStream(data);
streamSource.setInputStream(is);
if (item instanceof Base64BinaryDocument) {
Base64BinaryDocument b64doc = (Base64BinaryDocument) item;
String url = "xmldb:exist://" + b64doc.getUrl();
LOG.debug("Base64BinaryDocument detected, adding URL " + url);
streamSource.setSystemId(url);
}
} else {
LOG.error("Wrong item type " + Type.getTypeName(item.getType()));
throw new XPathException("wrong item type " + Type.getTypeName(item.getType()));
}
return streamSource;
}
/**
* Get input source for specified resource, containing inputStream and
* location. Used by @see Jing.
*/
public static InputSource getInputSource(Item s, XQueryContext context) throws XPathException, MalformedURLException, IOException {
StreamSource streamSource = getStreamSource(s, context);
InputSource inputSource = new InputSource();
inputSource.setByteStream(streamSource.getInputStream());
inputSource.setSystemId(streamSource.getSystemId());
return inputSource;
}
public static StreamSource getStreamSource(InputSource in) throws XPathException, MalformedURLException, IOException {
StreamSource streamSource = new StreamSource();
streamSource.setInputStream(in.getByteStream());
streamSource.setSystemId(in.getSystemId());
return streamSource;
}
/**
* Get URL value of item.
*/
public static String getUrl(Item item) throws XPathException {
String url = null;
if (item.getType() == Type.ANY_URI /*|| item.getType() != Type.STRING */) {
LOG.debug("Converting anyURI");
url = item.getStringValue();
} else if (item.getType() == Type.DOCUMENT || item.getType() == Type.NODE) {
LOG.debug("Retreiving URL from (document) node");
if (item instanceof NodeProxy) {
NodeProxy np = (NodeProxy) item;
url = np.getDocument().getBaseURI();
LOG.debug("Document detected, adding URL " + url);
}
}
if(url==null) {
throw new XPathException("Parameter should be of type xs:anyURI or document.");
}
if (url.startsWith("/")) {
url = "xmldb:exist://" + url;
}
return url;
}
/**
* Get URL values of sequence.
*/
public static String[] getUrls(Sequence s) throws XPathException {
ArrayList<String> urls = new ArrayList<String>();
SequenceIterator i = s.iterate();
while (i.hasNext()) {
Item next = i.nextItem();
String url = getUrl(next);
urls.add(url);
}
String returnUrls[] = new String[urls.size()];
returnUrls = urls.toArray(returnUrls);
return returnUrls;
}
/**
* Create validation report.
*/
static public NodeImpl writeReport(ValidationReport report, MemTreeBuilder builder) {
// start root element
int nodeNr = builder.startElement("", "report", "report", null);
// validation status: valid or invalid
builder.startElement("", "status", "status", null);
if (report.isValid()) {
builder.characters("valid");
} else {
builder.characters("invalid");
}
builder.endElement();
// namespace when available
if (report.getNamespaceUri() != null) {
builder.startElement("", "namespace", "namespace", null);
builder.characters(report.getNamespaceUri());
builder.endElement();
}
// validation duration
AttributesImpl durationAttribs = new AttributesImpl();
durationAttribs.addAttribute("", "unit", "unit", "CDATA", "msec");
builder.startElement("", "duration", "duration", durationAttribs);
builder.characters("" + report.getValidationDuration());
builder.endElement();
// print exceptions if any
if (report.getThrowable() != null) {
builder.startElement("", "exception", "exception", null);
String className = report.getThrowable().getClass().getName();
if (className != null) {
builder.startElement("", "class", "class", null);
builder.characters(className);
builder.endElement();
}
String message = report.getThrowable().getMessage();
if (message != null) {
builder.startElement("", "message", "message", null);
builder.characters(message);
builder.endElement();
}
String stacktrace = report.getStackTrace();
if (stacktrace != null) {
builder.startElement("", "stacktrace", "stacktrace", null);
builder.characters(stacktrace);
builder.endElement();
}
builder.endElement();
}
// reusable attributes
AttributesImpl attribs = new AttributesImpl();
// iterate validation report items, write message
List cr = report.getValidationReportItemList();
for (Iterator iter = cr.iterator(); iter.hasNext();) {
ValidationReportItem vri = (ValidationReportItem) iter.next();
// construct attributes
attribs.addAttribute("", "level", "level", "CDATA", vri.getTypeText());
attribs.addAttribute("", "line", "line", "CDATA", Integer.toString(vri.getLineNumber()));
attribs.addAttribute("", "column", "column", "CDATA", Integer.toString(vri.getColumnNumber()));
if (vri.getRepeat() > 1) {
attribs.addAttribute("", "repeat", "repeat", "CDATA", Integer.toString(vri.getRepeat()));
}
// write message
builder.startElement("", "message", "message", attribs);
builder.characters(vri.getMessage());
builder.endElement();
// Reuse attributes
attribs.clear();
}
// finish root element
builder.endElement();
// return result
return ((DocumentImpl) builder.getDocument()).getNode(nodeNr);
}
/**
* Safely close the input source and underlying inputstream.
*/
public static void closeInputSource(InputSource source){
if(source==null){
return;
}
InputStream is = source.getByteStream();
if(is==null){
return;
}
try {
is.close();
} catch (Exception ex){
LOG.error("Problem while closing inputstream. ("
+ getDetails(source) + ") "
+ ex.getMessage(), ex);
}
}
/**
* Safely close the stream source and underlying inputstream.
*/
public static void closeStreamSource(StreamSource source){
if(source==null){
return;
}
InputStream is = source.getInputStream();
if(is==null){
return;
}
try {
is.close();
} catch (Exception ex) {
LOG.error("Problem while closing inputstream. ("
+ getDetails(source) + ") "
+ ex.getMessage(), ex);
}
}
/**
* Safely close the stream sources and underlying inputstreams.
*/
public static void closeStreamSources(StreamSource sources[]){
if(sources==null){
return;
}
for(StreamSource source : sources){
closeStreamSource(source);
}
}
private static String getDetails(InputSource source) {
StringBuilder sb = new StringBuilder();
if(source.getPublicId()!=null){
sb.append("PublicId='");
sb.append(source.getPublicId());
sb.append("' ");
}
if(source.getSystemId()!=null){
sb.append("SystemId='");
sb.append(source.getSystemId());
sb.append("' ");
}
if(source.getEncoding()!=null){
sb.append("Encoding='");
sb.append(source.getEncoding());
sb.append("' ");
}
return sb.toString();
}
private static String getDetails(StreamSource source) {
StringBuilder sb = new StringBuilder();
if(source.getPublicId()!=null){
sb.append("PublicId='");
sb.append(source.getPublicId());
sb.append("' ");
}
if(source.getSystemId()!=null){
sb.append("SystemId='");
sb.append(source.getSystemId());
sb.append("' ");
}
return sb.toString();
}
}