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

org.openmdx.application.xml.spi.XMLTarget Maven / Gradle / Ivy

There is a newer version: 2.18.10
Show newest version
/*
 * ====================================================================
 * Project:     openMDX/Core, http://www.openmdx.org/
 * Description: XML Target 
 * Owner:       OMEX AG, Switzerland, http://www.omex.ch
 * ====================================================================
 *
 * This software is published under the BSD license as listed below.
 * 
 * Copyright (c) 2009-2014, OMEX AG, Switzerland
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 * * Neither the name of the openMDX team nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * ------------------
 * 
 * This product includes software developed by other organizations as
 * listed in the NOTICE file.
 */
package org.openmdx.application.xml.spi;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Stack;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.jmi.reflect.RefObject;
import javax.xml.datatype.XMLGregorianCalendar;

import org.openmdx.base.exception.ServiceException;
import org.openmdx.base.mof.cci.ModelElement_1_0;
import org.openmdx.base.mof.cci.Model_1_0;
import org.openmdx.base.mof.cci.Multiplicity;
import org.openmdx.base.mof.cci.PrimitiveTypes;
import org.openmdx.base.mof.spi.Model_1Factory;
import org.openmdx.base.naming.Path;
import org.openmdx.base.text.conversion.Base64;
import org.openmdx.base.text.conversion.XMLEncoder;
import org.openmdx.kernel.exception.BasicException;
import org.openmdx.kernel.jdo.ReducedJDOHelper;
import org.w3c.format.DateTimeFormat;

/**
 * XML Target
 */
public class XMLTarget implements ExportTarget {

    /**
     * Constructor 
     *
     * @param out
     * 
     * @throws ServiceException
     */
	public XMLTarget(
	    OutputStream out
	) throws ServiceException {
		this.xmlWriter = new XMLWriter(out);
		this.model = Model_1Factory.getModel();
	}

	/**
	 * The delegate
	 */
    private final XMLWriter xmlWriter;

    /**
     * 
     */
    public static final String FILE_EXT_XML = ".xml";
    
    /**
     * The model accessor
     */
    private final Model_1_0 model;
    
    /**
     * Convert a MOF name to its XML format
     * 
     * @param elementName
     * 
     * @return the corresponding XML name
     */
	protected static String toXML(
	    String elementName
	) {
		return elementName.replace(':', '.');
	}

	/**
	 * Extract the simple name from a qualified name
	 * 
	 * @param qualifiedName
	 * 
	 * @return the corresponding simple name
	 */
	private static String getSimpleName(String qualifiedName) {
		return qualifiedName.substring(qualifiedName.lastIndexOf(':') + 1);
	}

	/**
	 * TODO multi-valued qualifier support
	 * 
	 * Determine an object's qualifier name
	 * 
	 * @param object
	 * 
	 * @return the qualifier name
	 * 
	 * @throws ServiceException
	 */
    private String getQualifierName(
        RefObject object
    ) throws ServiceException {
        ModelElement_1_0 objectClass = this.model.getElement(object.refClass().refMofId());
        final Path compositeReference = (Path) objectClass.objGetValue("compositeReference");
        if(compositeReference != null) {
            ModelElement_1_0 compReference = this.model.getElement(compositeReference.getLastSegment().toClassicRepresentation());
            ModelElement_1_0 associationEnd = this.model.getElement(compReference.getReferencedEnd().getLastSegment().toClassicRepresentation());
            return (String) associationEnd.objGetList("qualifierName").get(0);
        }
        if("org:openmdx:base:Authority".equals(objectClass.getQualifiedName())) {
            return "name";
        }
        throw new ServiceException(
            BasicException.Code.DEFAULT_DOMAIN, 
            BasicException.Code.ASSERTION_FAILURE, 
            "no composite reference found for class.", 
            new BasicException.Parameter("class", objectClass)
        );
    }
	
    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#exportProlog()
     */
    public void exportProlog(
        boolean empty
    ) throws ServiceException {
        // nothing to do
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#exportProlog(org.openmdx.base.accessor.jmi.cci.RefObject_1_0)
     */
    public void startAuthority(
        String authority
    ) throws ServiceException {
        this.xmlWriter.startDocument(authority);
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#startObject(org.openmdx.application.xml.Exporter.TraversedObject, java.lang.String, java.lang.String, java.lang.String, java.lang.String)
     */
    public void startObject(
        RefObject refObject, 
        boolean noOperation
    ) throws ServiceException {
        String qualifierName = getQualifierName(refObject);
        String qualifiedTypeName = refObject.refClass().refMofId();
        Path objectId = (Path) ReducedJDOHelper.getObjectId(refObject);
        Map atts = new LinkedHashMap();
        String qualifierValue = objectId.getLastSegment().toClassicRepresentation();
        atts.put(qualifierName, qualifierValue);
        if(noOperation) {
            atts.put("_operation", "null");
        }
        if(qualifiedTypeName.equals("org:openmdx:base:Authority")) {
            String namespace = qualifierValue.substring(0, qualifierValue.lastIndexOf(":"));
            String modelName = qualifierValue.substring(qualifierValue.lastIndexOf(":") + 1);
            atts.put(
                "xmlns:xsi", 
                "http://www.w3.org/2001/XMLSchema-instance"
            );
            atts.put(
                "xsi:noNamespaceSchemaLocation", 
                "xri://+resource/" + namespace.replace(':', '/') + "/" + modelName + "/xmi1/" + modelName + ".xsd"
            );
        }
        this.xmlWriter.startElement("", "", toXML(qualifiedTypeName), atts, false);
	}

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#startAttributes()
     */
    public void startAttributes(boolean empty) throws ServiceException {
        this.xmlWriter.startElement("", "", "_object", null, empty);
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#startAttribute(java.lang.String, java.lang.String, java.lang.String, java.lang.Object, boolean)
     */
    public void startAttribute(
        String qualifiedName,
        String typeName,
        Multiplicity multiplicity,
        Object values,
        boolean empty
    ) throws ServiceException {
        this.xmlWriter.startElement("", "", getSimpleName(qualifiedName), null, empty);
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#write(java.lang.Object)
     */
    public void write(
        String typeName, 
        Multiplicity multiplicity, 
        int position, 
        Object value
    ) throws ServiceException {
        final String stringValue;
        if(PrimitiveTypes.DATETIME.equals(typeName)) {
            stringValue = DateTimeFormat.EXTENDED_UTC_FORMAT.format((Date) value);
        } else if(PrimitiveTypes.DATE.equals(typeName)) {
            stringValue = ((XMLGregorianCalendar) value).toXMLFormat();
        } else if(PrimitiveTypes.LONG.equals(typeName) || PrimitiveTypes.INTEGER.equals(typeName) || PrimitiveTypes.SHORT.equals(typeName)) {
            stringValue = String.valueOf(((Number) value).longValue());
        } else if(PrimitiveTypes.BINARY.equals(typeName)) {
            stringValue = value instanceof byte[] ? Base64.encode((byte[]) value) : value.toString();
        } else if(value instanceof Path) {
            stringValue = ((Path)value).toXRI();
        } else {
            stringValue = value.toString();
        }
        if(multiplicity.isMultiValued()) {
            Map atts = new LinkedHashMap();
            if((multiplicity == Multiplicity.SPARSEARRAY)) {
                atts.put("_position", String.valueOf(position));
            }
            this.xmlWriter.startElement("", "", "_item", atts, false);
            this.xmlWriter.write(stringValue);
            this.xmlWriter.endElement("", "", "_item", false);
        } else {
            this.xmlWriter.write(stringValue);
        }
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#endAttribute(java.lang.String, java.lang.String, java.lang.String, java.lang.Object, boolean)
     */
    public void endAttribute(
        String qualifiedName,
        String typeName,
        Multiplicity multiplicity,
        Object values,
        boolean empty
    ) throws ServiceException {
        if(!empty) {
            this.xmlWriter.endElement("", "", getSimpleName(qualifiedName), multiplicity.isMultiValued());
        }
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#endAttributes(boolean)
     */
    public void endAttributes(
        boolean empty
    ) throws ServiceException {
        if(!empty) {
            this.xmlWriter.endElement("", "", "_object", true);
        }
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#beginReferences()
     */
    public void startChildren(
        boolean empty
    ) throws ServiceException {
        this.xmlWriter.startElement("", "", "_content", null, empty);
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#startReference(java.lang.String)
     */
    public void startReference(
        String name, 
        boolean empty
    ) throws ServiceException {
		this.xmlWriter.startElement("", "", getSimpleName(name), null, empty);
	}

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#endReference(java.lang.String)
     */
    public void endReference(
        String reference, 
        boolean empty
    ) throws ServiceException {
        if(!empty){
            this.xmlWriter.endElement("", "", getSimpleName(reference), true);
        }
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#endReferences()
     */
    public void endChildren(
        boolean empty
    ) throws ServiceException {
        if(!empty) {
            this.xmlWriter.endElement("", "", "_content", true);
        }
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#endObject(org.openmdx.application.xml.Exporter.TraversedObject, org.openmdx.base.accessor.jmi.cci.RefObject_1_0, java.lang.String)
     */
    public void endObject(
        RefObject refObject
    ) throws ServiceException {
        String qualifiedName = refObject.refClass().refMofId();
        this.xmlWriter.endElement("", "", toXML(qualifiedName), true);
    }

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#exportEpilog(org.openmdx.base.accessor.jmi.cci.RefObject_1_0, org.openmdx.application.xml.Exporter.TraversedObject)
     */
    public void endAuthority(
        String authority
    ) throws ServiceException {
		this.xmlWriter.endDocument();
	}

    /* (non-Javadoc)
     * @see org.openmdx.application.xml.spi.ExportTarget#exportEpilog()
     */
    public void exportEpilog(
        boolean empty
    ) throws ServiceException {
        // nothing to do
    }

	
    // -----------------------------------------------------------------------
	// Class XMLWriter
    // -----------------------------------------------------------------------
	
	/**
	 * XML Writer
	 */
    static class XMLWriter {

        /**
         * Constructor 
         *
         * @param out
         * 
         * @throws ServiceException
         */
        protected XMLWriter(
            OutputStream out
        ) throws ServiceException {
            this.outputStream = out;
            this.qNameStack = new Stack();
        }

        /**
         * The character encoding to be used
         */
        private static final String ENCODING = "UTF-8";

        /**
         * The binary stream
         */
        private final OutputStream outputStream;

        /**
         * The text stream
         */
        private PrintStream printStream;

        /**
         * Indentation by TAB
         */
        private final static String INDENT = "\t";

        /**
         * 
         */
        private final Stack qNameStack;

        /**
         * Tells whether the documents are added to an archive
         * 
         * @return true if the documents are added to an archive
         */
        private boolean isMultiFileExport(){
            return this.outputStream instanceof ZipOutputStream;
        }
        
        /**
         * Start Document
         * 
         * @throws ServiceException
         */
        public void startDocument(
            String authority
        ) throws ServiceException {
            if(isMultiFileExport()) try {
                ((ZipOutputStream) this.outputStream).putNextEntry(
                    new ZipEntry(toXML(authority) + FILE_EXT_XML)
                );
            } catch (IOException exception) {
                throw new ServiceException(exception);
            }
            try {
                this.printStream = new PrintStream(this.outputStream, false, ENCODING);
            } catch (UnsupportedEncodingException exception) {
                throw new ServiceException(exception);
            }
            this.printStream.print("");
        }

        /**
         * Start Element
         * 
         * @param namespaceURI
         * @param localName
         * @param qName
         * @param attributes
         * @param empty
         * 
         * @throws ServiceException
         */
        public void startElement(
            String namespaceURI, 
            String localName, 
            String qName, 
            Map attributes, 
            boolean empty
        ) throws ServiceException {
            this.printStream.println();
            for (int i = 0; i < this.qNameStack.size(); i++) {
                this.printStream.print(INDENT);
            }
            this.printStream.print("<");
            this.printStream.print(qName);
            if(attributes != null) {
                for(Map.Entry attribute : attributes.entrySet()) {
                    this.printStream.print(" ");
                    this.printStream.print(XMLEncoder.encode(attribute.getKey()));
                    this.printStream.print("=\"");
                    this.printStream.print(XMLEncoder.encode(attribute.getValue()));
                    this.printStream.print("\"");
                }
            }
            if(empty) {
                this.printStream.print("/>");
            } else {
                this.printStream.print(">");
                this.qNameStack.push(qName);
            }
        }

        /**
         * Print XML Encoded Value
         * 
         * @param value the value to be written
         * 
         * @throws ServiceException
         */
        public void write(
            String value
        ) throws ServiceException {
            this.printStream.print(XMLEncoder.encode(value));
        }

        /**
         * End Element
         * 
         * @param namespaceURI
         * @param localName
         * @param qName
         * @param newLine
         * 
         * @throws ServiceException
         */
        public void endElement(
            String namespaceURI, 
            String localName, 
            String qName, 
            boolean newLine
        ) throws ServiceException {
            String expectedQName = this.qNameStack.pop();
            if(!expectedQName.equals(qName)) {
                throw new ServiceException(
                    BasicException.Code.DEFAULT_DOMAIN, 
                    BasicException.Code.ASSERTION_FAILURE, 
                    "Non matching qName for XML tag.", 
                    new BasicException.Parameter("qName", qName),
                    new BasicException.Parameter("expected qName", expectedQName)
                );
            }
            if(newLine) {
                this.printStream.println();
                for (int i = this.qNameStack.size(); i > 0; i--) {
                    this.printStream.print(INDENT);
                }
            }
            this.printStream.print("");
        }

        /**
         * Comment
         * 
         * @param comment
         * 
         * @throws ServiceException
         */
        public void comment(String comment) throws ServiceException {
            this.printStream.print("");
        }

        /**
         * Processing Instruction
         * 
         * @param target
         * @param data
         * 
         * @throws ServiceException
         */
        public void processingInstruction(String target, String data) throws ServiceException {
            this.printStream.println();
            this.printStream.print("");
        }
        /**
         * End Document
         * 
         * @throws ServiceException
         */
        public void endDocument(
        ) throws ServiceException {
            this.printStream.close();
            if(!this.qNameStack.isEmpty()) {
                throw new ServiceException(
                    BasicException.Code.DEFAULT_DOMAIN, 
                    BasicException.Code.ASSERTION_FAILURE, 
                    "Open elements while endDocument().", 
                    new BasicException.Parameter("elements", this.qNameStack)
                );
            }
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy