Package io.apigee.trireme.util

Source Code of io.apigee.trireme.util.XmlWrap$Job

/**
* Copyright 2014 Apigee Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.apigee.trireme.util;

import io.apigee.trireme.core.InternalNodeModule;
import io.apigee.trireme.core.NodeRuntime;
import io.apigee.trireme.core.ScriptTask;
import io.apigee.trireme.core.Utils;
import io.apigee.trireme.core.internal.NodeOSException;
import io.apigee.trireme.core.modules.Buffer;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.annotations.JSFunction;

import javax.xml.XMLConstants;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import static io.apigee.trireme.core.ArgUtils.*;

import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class XmlWrap
    implements InternalNodeModule
{
    public static final int DEFAULT_MAX_JOBS = 8;

    @Override
    public String getModuleName() {
        return "xml-wrap";
    }

    @Override
    public Scriptable registerExports(Context cx, Scriptable global, NodeRuntime runtime)
        throws InvocationTargetException, IllegalAccessException, InstantiationException
    {
        ScriptableObject.defineClass(global, XmlWrapImpl.class);
        XmlWrapImpl wrap = (XmlWrapImpl)cx.newObject(global, XmlWrapImpl.CLASS_NAME);
        wrap.initTransformer(cx, runtime);
        return wrap;
    }

    public static class XmlWrapImpl
        extends ScriptableObject
    {
        public static final String CLASS_NAME = "_xmlWrapClass";

        private TransformerFactory transFactory;
        private NodeRuntime runtime;
        private int availableSlots = DEFAULT_MAX_JOBS;
        private final ArrayDeque<Job> jobQueue = new ArrayDeque<Job>();

        @Override
        public String getClassName() {
            return CLASS_NAME;
        }

        void initTransformer(Context cx, NodeRuntime runtime)
        {
            transFactory = TransformerFactory.newInstance();
            configureTransformer(cx);
            this.runtime = runtime;

            String maxJobs = System.getProperty("trireme.max.xslt.jobs");
            if (maxJobs != null) {
                availableSlots = Integer.parseInt(maxJobs);
            }
        }

        private void configureTransformer(Context cx)
        {
            try {
                transFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
            } catch (TransformerConfigurationException tce) {
                throw Utils.makeError(cx, this, "Error configuring XML transformer: " + tce);
            }
        }

        @JSFunction
        @SuppressWarnings("unused")
        public static void setTransformer(Context cx, Scriptable thisObj, Object[] args, Function funObj)
        {
            String className = stringArg(args, 0);
            XmlWrapImpl self = (XmlWrapImpl)thisObj;

            self.transFactory =
                TransformerFactory.newInstance(className, XmlWrap.class.getClassLoader());
            self.configureTransformer(cx);
        }

        @JSFunction
        @SuppressWarnings("unused")
        public static Object createStylesheet(Context cx, Scriptable thisObj, Object[] args, Function funObj)
        {
            ensureArg(args, 0);
            XmlWrapImpl self = (XmlWrapImpl)thisObj;

            Source src = self.getSource(cx, args[0]);

            try {
                // Keep in mind that we are in Node.js and the factory will be single-threaded.
                // Set the error listener to prevent stuff being written to standard error.
                Err errs = new Err();
                self.transFactory.setErrorListener(errs);
                Templates tmpl = self.transFactory.newTemplates(src);

                ScriptableObject ret = (ScriptableObject)cx.newObject(thisObj);
                ret.associateValue("template", tmpl);
                return ret;

            } catch (TransformerConfigurationException e) {
                throw Utils.makeError(cx, thisObj, "XSLT transformer exception: " + e);
            }
        }

        @JSFunction
        @SuppressWarnings("unused")
        public static Object createDocument(Context cx, Scriptable thisObj, Object[] args, Function funObj)
        {
            ensureArg(args, 0);
            XmlWrapImpl self = (XmlWrapImpl)thisObj;

            Source src = self.getSource(cx, args[0]);
            ScriptableObject ret = (ScriptableObject)cx.newObject(thisObj);
            ret.associateValue("document", src);
            return ret;
        }

        @JSFunction
        @SuppressWarnings("unused")
        public static Object transform(Context cx, Scriptable thisObj, Object[] args, Function funObj)
        {
            ScriptableObject ss = objArg(args, 0, ScriptableObject.class, true);
            ScriptableObject doc = objArg(args, 1, ScriptableObject.class, true);
            Scriptable params = objArg(args, 2, Scriptable.class, false);
            Function callback = functionArg(args, 3, false);
            XmlWrapImpl self = (XmlWrapImpl)thisObj;

            Templates tmpl = (Templates)ss.getAssociatedValue("template");
            if (tmpl == null) {
                throw Utils.makeError(cx, thisObj, "Stylesheet was not created by createStylesheet");
            }

            Source src = (Source)doc.getAssociatedValue("document");
            if (src == null) {
                throw Utils.makeError(cx, thisObj, "Document was not created by createDocument");
            }

            if ((callback == null) || Undefined.instance.equals(callback)) {
                // Synchronous case
                try {
                    return self.doTransform(tmpl, src, params);
                } catch (NodeOSException nse) {
                    throw Utils.makeError(cx, thisObj, nse);
                }

            } else {
                Job job = new Job(tmpl, src, params, callback);
                self.jobQueue.add(job);
                self.scheduleJobs();

                return Undefined.instance;
            }
        }

        private void scheduleJobs()
        {
            while (!jobQueue.isEmpty() && (availableSlots > 0)) {
                Job job = jobQueue.poll();
                availableSlots--;
                assert(availableSlots >= 0);
                scheduleJob(job);
            }
        }

        private void scheduleJob(final Job job)
        {
            runtime.pin();
            runtime.getAsyncPool().execute(new Runnable()
            {
                @Override
                public void run()
                {
                    try {
                        final String result = doTransform(job.tmpl, job.src, job.params);
                        runtime.enqueueTask(new ScriptTask()
                        {
                            @Override
                            public void execute(Context cx, Scriptable scope)
                            {
                                availableSlots++;
                                scheduleJobs();
                                job.callback.call(cx, job.callback, XmlWrapImpl.this, new Object[]{
                                    Undefined.instance, result});

                            }
                        });

                    } catch (final NodeOSException nse) {
                        runtime.enqueueTask(new ScriptTask()
                        {
                            @Override
                            public void execute(Context cx, Scriptable scope)
                            {
                                availableSlots++;
                                scheduleJobs();
                                job.callback.call(cx, job.callback, XmlWrapImpl.this, new Object[]{
                                    Utils.makeErrorObject(cx, XmlWrapImpl.this, nse)
                                });
                            }
                        });
                    } finally {
                        runtime.unPin();
                    }
                }
            });
        }

        String doTransform(Templates tmpl, Source src, Scriptable params)
            throws NodeOSException
        {
            StringWriter output = new StringWriter();
            StreamResult result = new StreamResult(output);

            Err errs = new Err();
            try {
                Transformer trans = tmpl.newTransformer();
                trans.setErrorListener(errs);
                trans.setOutputProperty(OutputKeys.INDENT, "yes");

                if (params != null) {
                    for (Object id : params.getIds()) {
                        if (id instanceof String) {
                            String name = (String)id;
                            String val = Context.toString(params.get(name, params));
                            trans.setParameter(name, val);
                        }
                    }
                }

                trans.transform(src, result);

            } catch (TransformerConfigurationException tce) {
                throw new NodeOSException(tce.toString());
            } catch (TransformerException e) {
                // Fall through! We already collected them below
            }

            // Use the error handler to collect all the errors, not just the first one.
            if (!errs.getErrors().isEmpty()) {
                StringBuilder msgs = new StringBuilder();
                for (TransformerException te : errs.getErrors()) {
                    msgs.append(te.getLocationAsString() + ": " + te.toString() + '\n');
                }
                throw new NodeOSException(msgs.toString());
            }

            return output.toString();
        }

        private Source getSource(Context cx, Object o)
        {
            if (o instanceof String) {
                return new StreamSource(new StringReader((String)o));

            } else if (o instanceof Buffer.BufferImpl) {
                Buffer.BufferImpl buf = (Buffer.BufferImpl)o;
                ByteArrayInputStream bis =
                    new ByteArrayInputStream(buf.getArray(), buf.getArrayOffset(), buf.getLength());
                return new StreamSource(bis);

            } else {
                throw Utils.makeError(cx, this, "Input must be a string or a buffer");
            }
        }
    }

    private static final class Err
        implements ErrorListener
    {
        private List<TransformerException> errs = Collections.emptyList();

        List<TransformerException> getErrors() {
            return errs;
        }

        @Override
        public void warning(TransformerException exception)
        {
            // Ignore warnings
        }

        @Override
        public void error(TransformerException exception)
        {
            // Collect errors
            if (errs.isEmpty()) {
                errs = new ArrayList<TransformerException>();
            }
            errs.add(exception);
        }

        @Override
        public void fatalError(TransformerException exception)
            throws TransformerException
        {
            // Collect fatal errors and re-throw to make the transformer exit.
            error(exception);
            throw exception;
        }
    }

    private static final class Job
    {
        Templates tmpl;
        Source src;
        Scriptable params;
        Function callback;

        Job(Templates tmpl, Source src, Scriptable params, Function callback)
        {
            this.tmpl = tmpl;
            this.src = src;
            this.params = params;
            this.callback = callback;
        }
    }
}
TOP

Related Classes of io.apigee.trireme.util.XmlWrap$Job

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.