All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xmlcalabash.io.ReadableData Maven / Gradle / Ivy

There is a newer version: 1.1.20-p16-98
Show newest version
/*
 * ReadableData.java
 *
 * Copyright 2008 Mark Logic Corporation.
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */

package com.xmlcalabash.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;

import com.xmlcalabash.util.*;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XdmNode;

import org.json.JSONTokener;

import com.xmlcalabash.core.XProcConstants;
import com.xmlcalabash.core.XProcException;
import com.xmlcalabash.core.XProcRuntime;
import com.xmlcalabash.io.DataStore.DataReader;
import com.xmlcalabash.model.Step;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author ndw
 */
public class ReadableData implements ReadablePipe {
    public static final QName _contentType = new QName("","content-type");
    public static final QName c_contentType = new QName("c",XProcConstants.NS_XPROC_STEP, "content-type");
    public static final QName _encoding = new QName("","encoding");
    public static final QName c_encoding = new QName("c",XProcConstants.NS_XPROC_STEP, "encoding");
    private String contentType = null;
    private Logger logger = LoggerFactory.getLogger(ReadablePipe.class);
    private int pos = 0;
    private QName wrapper = null;
    private String uri = null;
    private InputStream inputStream = null;
    private String serverContentType = "content/unknown";
    private XProcRuntime runtime = null;
    private DocumentSequence documents = null;
    private Step reader = null;

    /* Creates a new instance of ReadableDocument */
    public ReadableData(XProcRuntime runtime, QName wrapper, String uri, String contentType) {
        this(runtime, wrapper, uri, null, contentType);
    }

    public ReadableData(XProcRuntime runtime, QName wrapper, InputStream inputStream, String contentType) {
        this(runtime, wrapper, null, inputStream, contentType);
    }

    private ReadableData(XProcRuntime runtime, QName wrapper, String uri, InputStream inputStream, String contentType) {
        this.runtime = runtime;
        this.uri = uri;
        this.inputStream = inputStream;
        this.wrapper = wrapper;
        this.contentType = contentType;
    }

    private DocumentSequence ensureDocuments() {
        if (documents != null) {
            return documents;
        }

        documents = new DocumentSequence(runtime);

        if ((uri == null) && (inputStream == null)) {
            return documents;
        }

		try {
			final String userContentType = parseContentType(contentType);
			if (uri == null) {
			    try {
			        read(userContentType, null, inputStream, getContentType());
			    } finally {
			        // This is the only case where the inputStream should be 
			        // closed.
			        inputStream.close();
			    }
			} else if ("-".equals(uri)) {
				read(userContentType, getDataUri("-"), System.in,
						getContentType());
				// No need to close the input stream here, since it's System.in.
			} else {
				String accept = "application/json, text/json, text/*, */*";
				if (userContentType != null) {
					accept = userContentType + ", */*";
				}
				DataStore store = runtime.getDataStore();
				store.readEntry(uri, uri, accept, null, new DataReader() {
					public void load(URI dataURI, String serverContentType,
							InputStream stream, long len) throws IOException {
						read(userContentType, dataURI, stream,
								serverContentType);
					}
				});
				// Also no need to close the input stream here, since  
				// DataStore implementations close the input stream when
				// necessary.
			}
		} catch (IOException ioe) {
            throw new XProcException(XProcConstants.dynamicError(29), ioe);
        }
        return documents;
    }

    
    // This method does NOT close the InputStream; it relies on the caller to close
    // the InputStream, as appropriate.
	private void read(String userContentType, URI dataURI,
			InputStream stream, String serverContentType)
			throws IOException {
		this.serverContentType = serverContentType;

        contentType = serverContentType;

        // If the input is - or a file: URI, assume the user provided content type
        // and charset are correct.
        if ((uri != null) && ("-".equals(uri) || "file".equals(dataURI.getScheme()))) {
            if (userContentType != null) {
                contentType = userContentType;
            }
        }

        String charset = parseCharset(contentType);

        TreeWriter tree = new TreeWriter(runtime);
        tree.startDocument(dataURI);
		if (contentType != null && "content/unknown".equals(serverContentType)) {
		    // pretend...
		    serverContentType = contentType;
		}

		String serverBaseContentType = parseContentType(serverContentType);
		String serverCharset = parseCharset(serverContentType);

		if (serverCharset != null) {
		    // HACK! Make the content type here consistent with the content type returned
		    // from the http-request tests, just to make the test suite results more
		    // consistent.
		    serverContentType = serverBaseContentType + "; charset=\"" + serverCharset + "\"";
		}

		if (runtime.transparentJSON() && HttpUtils.jsonContentType(contentType)) {
		    if (charset == null) {
		        // FIXME: Is this right? I think it is...
		        charset = "UTF-8";
		    }
		    InputStreamReader reader = new InputStreamReader(stream, charset);
		    JSONTokener jt = new JSONTokener(reader);
		    XdmNode jsonDoc = JSONtoXML.convert(runtime.getProcessor(), jt, runtime.jsonFlavor());
		    tree.addSubtree(jsonDoc);
		} else {
		    tree.addStartElement(wrapper);
		    if (XProcConstants.c_data.equals(wrapper)) {
		        if ("content/unknown".equals(contentType)) {
		            tree.addAttribute(_contentType, "application/octet-stream");
		        } else {
		            tree.addAttribute(_contentType, contentType);
		        }
		        if (!isText(contentType, charset)) {
		            tree.addAttribute(_encoding, "base64");
		        }
		    } else {
		        if ("content/unknown".equals(contentType)) {
		            tree.addAttribute(c_contentType, "application/octet-stream");
		        } else {
		            tree.addAttribute(c_contentType, contentType);
		        }
		        if (!isText(contentType, charset)) {
		            tree.addAttribute(c_encoding, "base64");
		        }
		    }
		    tree.startContent();

		    if (isText(contentType, charset)) {
		        BufferedReader bufread;
		        if (charset == null) {
		            // FIXME: Is this right? I think it is...
		            charset = "UTF-8";
		        }
		        BufferedReader bufreader = new BufferedReader(new InputStreamReader(stream, charset));
		        int maxlen = 4096 * 3;
		        char[] chars = new char[maxlen];
		        int read = bufreader.read(chars, 0, maxlen);
		        while (read >= 0) {
		            if (read > 0) {
		                String data = new String(chars, 0, read);
		                tree.addText(data);
		            }
		            read = bufreader.read(chars, 0, maxlen);
		        }
		        bufreader.close();
		    } else {
		        // Fill the buffer each time so that we get an even number of base64 lines
		        int maxlen = 4096 * 3;
		        byte[] bytes = new byte[maxlen];
		        int pos = 0;
		        int readlen = maxlen;
		        boolean done = false;
		        while (!done) {
		            int read = stream.read(bytes, pos, readlen);
		            if (read >= 0) {
		                pos += read;
		                readlen -= read;
		            } else {
		                done = true;
		            }

		            if ((readlen == 0) || done) {
		                String base64 = Base64.encodeBytes(bytes, 0, pos);
		                tree.addText(base64 + "\n");
		                pos = 0;
		                readlen = maxlen;
		            }
		        }
		        stream.close();
		    }
		    tree.addEndElement();
		}

        tree.endDocument();

        documents.add(tree.getResult());
	}

    public void canReadSequence(boolean sequence) {
        // nop; always false
    }

    public boolean readSequence() {
        return false;
    }

    public void resetReader() {
        pos = 0;
    }

    public void setReader(Step step) {
        reader = step;
    }

    public void setNames(String stepName, String portName) {
        // nop;
    }

    public boolean moreDocuments() {
        DocumentSequence docs = ensureDocuments();
        return pos < docs.size();
    }

    public boolean closed() {
        return true;
    }

    public int documentCount() {
        DocumentSequence docs = ensureDocuments();
        return docs.size();
    }

    public DocumentSequence documents() {
        return ensureDocuments();
    }

    public XdmNode read() throws SaxonApiException {
        DocumentSequence docs = ensureDocuments();
        XdmNode doc = docs.get(pos++);
        if (reader != null) {
            logger.trace(MessageFormatter.nodeMessage(reader.getNode(),
                    reader.getName() + " read '" + (doc == null ? "null" : doc.getBaseURI()) + "' from " + this));
        }
        return doc;
    }

    // =======================================================================

    protected URI getDataUri(String uri) {
        try {
            return new URI(uri);
        } catch (URISyntaxException use) {
            throw new XProcException(use);
        }
    }

    protected String getContentType() {
        return serverContentType;
    }

    // =======================================================================

    private boolean isText(String contentType, String charset) {
        return ("application/xml".equals(contentType)
                || contentType.endsWith("+xml")
                || contentType.startsWith("text/")
                || "utf-8".equals(charset));
    }

    private String parseContentType(String contentType) {
        if (contentType == null) {
            return null;
        }

        int pos = contentType.indexOf(";");
        if (pos > 0) {
            String type = contentType.substring(0,pos).trim();
            return type;
        } else {
            return contentType;
        }
    }

    private String parseCharset(String contentType) {
        String charset = HttpUtils.getCharset(contentType);

        if (charset != null) {
            return charset.toLowerCase();
        }

        return null;
    }
}






© 2015 - 2024 Weber Informatics LLC | Privacy Policy