org.kalypso.ogc.sensor.zml.ObservationMarshaller.java Source code

Java tutorial

Introduction

Here is the source code for org.kalypso.ogc.sensor.zml.ObservationMarshaller.java

Source

/*----------------    FILE HEADER KALYPSO ------------------------------------------
 *
 *  This file is part of kalypso.
 *  Copyright (C) 2004 by:
 *
 *  Technical University Hamburg-Harburg (TUHH)
 *  Institute of River and coastal engineering
 *  Denickestrae 22
 *  21073 Hamburg, Germany
 *  http://www.tuhh.de/wb
 *
 *  and
 *
 *  Bjoernsen Consulting Engineers (BCE)
 *  Maria Trost 3
 *  56070 Koblenz, Germany
 *  http://www.bjoernsen.de
 *
 *  This library 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.1 of the License, or (at your option) any later version.
 *
 *  This library 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Contact:
 *
 *  E-Mail:
 *  belger@bjoernsen.de
 *  schlienger@bjoernsen.de
 *  v.doemming@tuhh.de
 *
 *  ---------------------------------------------------------------------------*/
package org.kalypso.ogc.sensor.zml;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;

import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.kalypso.commons.bind.JaxbUtilities;
import org.kalypso.commons.bind.NamespacePrefixMap;
import org.kalypso.commons.factory.FactoryException;
import org.kalypso.commons.parser.impl.DateParser;
import org.kalypso.contribs.java.xml.XMLUtilities;
import org.kalypso.core.KalypsoCorePlugin;
import org.kalypso.core.i18n.Messages;
import org.kalypso.ogc.sensor.IAxis;
import org.kalypso.ogc.sensor.IObservation;
import org.kalypso.ogc.sensor.ITupleModel;
import org.kalypso.ogc.sensor.SensorException;
import org.kalypso.ogc.sensor.metadata.IMetadataConstants;
import org.kalypso.ogc.sensor.metadata.ITimeseriesConstants;
import org.kalypso.ogc.sensor.metadata.MetadataList;
import org.kalypso.ogc.sensor.request.IRequest;
import org.kalypso.zml.AxisType;
import org.kalypso.zml.AxisType.ValueArray;
import org.kalypso.zml.MetadataListType;
import org.kalypso.zml.MetadataType;
import org.kalypso.zml.ObjectFactory;
import org.kalypso.zml.Observation;

/**
 * @author Gernot Belger
 */
class ObservationMarshaller {
    private static Logger LOG = Logger.getLogger(ObservationMarshaller.class.getName());

    /**
     * FIXME: Use {@link javax.xml.bind.DatatypeConverter#printDate(java.util.Calendar)} instead.
     */
    private static final String XML_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";//$NON-NLS-1$

    static final NamespacePrefixMap ZML_PREFIX_MAPPER = new NamespacePrefixMap("zml.kalypso.org"); //$NON-NLS-1$
    static {
        ZML_PREFIX_MAPPER.addMapping("filters.zml.kalypso.org", "filters"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    private static final Comparator<MetadataType> METADATA_COMPERATOR = new Comparator<MetadataType>() {
        @Override
        public int compare(final MetadataType o1, final MetadataType o2) {
            return o1.getName().compareTo(o2.getName());
        }
    };

    private final ObjectFactory m_factory = new ObjectFactory();

    /** Timezone with that all dates are written. */
    private final TimeZone m_timezone = KalypsoCorePlugin.getDefault().getTimeZone();

    private final IObservation m_input;

    private final IRequest m_request;

    public ObservationMarshaller(final IObservation input, final IRequest request) {
        m_input = input;
        m_request = request;
    }

    public void marshall(final Writer writer) throws SensorException {
        try {
            final Observation xml = createXML();
            final Marshaller marshaller = getMarshaller();
            marshaller.marshal(xml, writer);
        } catch (final JAXBException e) {
            LOG.log(Level.WARNING, Messages.getString("org.kalypso.ogc.sensor.zml.ZmlFactory.30"), e); //$NON-NLS-1$

            throw new SensorException(e);
        } catch (final FactoryException e) {
            LOG.log(Level.WARNING, Messages.getString("org.kalypso.ogc.sensor.zml.ZmlFactory.30"), e); //$NON-NLS-1$

            throw new SensorException(e);
        }
    }

    public void marshall(final OutputStream os) throws SensorException {
        try {
            final Observation xml = createXML();
            final Marshaller marshaller = getMarshaller();
            marshaller.marshal(xml, os);
        } catch (final JAXBException e) {
            LOG.log(Level.WARNING, Messages.getString("org.kalypso.ogc.sensor.zml.ZmlFactory.30"), e); //$NON-NLS-1$

            throw new SensorException(e);
        } catch (final FactoryException e) {
            LOG.log(Level.WARNING, Messages.getString("org.kalypso.ogc.sensor.zml.ZmlFactory.30"), e); //$NON-NLS-1$

            throw new SensorException(e);
        }
    }

    public void marshall(final File targetFile) throws SensorException {
        final String name = targetFile.getName();
        final File dir = targetFile.getParentFile();

        final File saveFile = new File(dir, name + ".saving"); //$NON-NLS-1$
        final File backupFile = new File(dir, name + "~"); //$NON-NLS-1$

        /* Delete old backup file if it still exists */
        FileUtils.deleteQuietly(backupFile);

        try (OutputStream outs = openOutputStream(saveFile)) {
            marshall(outs);

            /* Explicitly close, else next move fails */
            outs.close();

            /* backup old target */
            if (targetFile.exists())
                FileUtils.moveFile(targetFile, backupFile);

            /* rename saved file to target */
            FileUtils.moveFile(saveFile, targetFile);

            /* delete backup, we have successfully saved the file */
            FileUtils.deleteQuietly(backupFile);
        } catch (final IOException | OutOfMemoryError e) {
            LOG.log(Level.WARNING, Messages.getString("org.kalypso.ogc.sensor.zml.ZmlFactory.30"), e); //$NON-NLS-1$

            throw new SensorException(e);
        } finally {
            FileUtils.deleteQuietly(saveFile);
        }
    }

    private OutputStream openOutputStream(final File file) throws IOException {
        final OutputStream fileStream = new BufferedOutputStream(FileUtils.openOutputStream(file));

        /* Handle compressed zml */
        final String extension = FilenameUtils.getExtension(file.getName());
        if ("zmlz".equalsIgnoreCase(extension)) //$NON-NLS-1$
        {
            final short bufferSize = Short.MAX_VALUE; // 32KB
            return new GZIPOutputStream(fileStream, bufferSize);
        }

        return fileStream;
    }

    public String asString() {
        try {
            final ByteArrayOutputStream bos = new ByteArrayOutputStream();
            marshall(bos);
            bos.close();
            return new String(bos.toByteArray(), "UTF-8");//$NON-NLS-1$
        } catch (final Exception e) {
            e.printStackTrace();
            return e.toString();
        }
    }

    private static Marshaller getMarshaller() throws JAXBException {
        return JaxbUtilities.createMarshaller(ZmlFactory.JC, true, null, ZML_PREFIX_MAPPER);
    }

    /**
     * Create an XML-Observation ready for marshalling.
     *
     * @param timezone
     *          the time zone into which dates should be converted before serialised
     */
    private Observation createXML() throws FactoryException {
        try {
            // first of all fetch values
            final ITupleModel values = m_input.getValues(m_request);

            final MetadataList obsMetadataList = m_input.getMetadataList();

            final Observation obsType = m_factory.createObservation();

            obsType.setName(m_input.getName());
            final String metaName = obsMetadataList.getProperty(IMetadataConstants.MD_NAME, null);
            if (metaName != null)
                obsType.setName(metaName);

            final MetadataListType metadataListType = createMetadata(obsMetadataList);
            obsType.setMetadataList(metadataListType);

            // sort axes, this is not needed from a XML view, but very useful when comparing marshaled files (e.g.
            // JUnit-Test)
            final List<AxisType> axisList = obsType.getAxis();
            final Comparator<? super IAxis> axisComparator = new MarshallerAxisComparator();
            final SortedSet<IAxis> sortedAxis = new TreeSet<>(axisComparator);
            sortedAxis.addAll(Arrays.asList(m_input.getAxes()));

            for (final IAxis axis : sortedAxis) {
                if (axis.isPersistable()) {
                    final AxisType axisType = buildAxis(values, axis);

                    axisList.add(axisType);
                }
            }

            return obsType;
        } catch (final Exception e) {
            throw new FactoryException(e);
        }
    }

    private AxisType buildAxis(final ITupleModel values, final IAxis axis) throws SensorException {
        final AxisType axisType = m_factory.createAxisType();

        final String xsdType = ZmlParserFactory.getXSDTypeFor(axis.getDataClass().getName());

        axisType.setDatatype(xsdType);
        axisType.setName(axis.getName());
        axisType.setUnit(axis.getUnit());
        axisType.setType(axis.getType());
        axisType.setKey(axis.isKey());

        final ValueArray valueArrayType = m_factory.createAxisTypeValueArray();

        valueArrayType.setSeparator(";"); //$NON-NLS-1$
        valueArrayType.setValue(buildValueString(values, axis));

        axisType.setValueArray(valueArrayType);
        return axisType;
    }

    private MetadataListType createMetadata(final MetadataList obsMetadataList) {
        final MetadataListType metadataListType = m_factory.createMetadataListType();
        final List<MetadataType> metadataList = metadataListType.getMetadata();
        for (final Entry<Object, Object> entry : obsMetadataList.entrySet()) {
            final String mdKey = (String) entry.getKey();
            final String mdValue = (String) entry.getValue();

            final MetadataType mdType = m_factory.createMetadataType();
            mdType.setName(mdKey);

            // TRICKY: if this looks like an xml-string then pack it
            // into a CDATA section and use the 'data'-Element instead
            if (mdValue.startsWith(XMLUtilities.XML_HEADER_BEGIN))
                mdType.setData(XMLUtilities.encapsulateInCDATA(mdValue));
            else
                mdType.setValue(mdValue);

            metadataList.add(mdType);
        }

        Collections.sort(metadataList, METADATA_COMPERATOR);

        // write time zone info into meta data
        final MetadataType mdType = m_factory.createMetadataType();
        mdType.setName(ITimeseriesConstants.MD_TIMEZONE);
        mdType.setValue(m_timezone.getID());
        // Check, if value already exists and remove first
        for (final Iterator<MetadataType> iterator = metadataList.iterator(); iterator.hasNext();) {
            if (iterator.next().getName().equals(ITimeseriesConstants.MD_TIMEZONE))
                iterator.remove();
        }
        metadataList.add(mdType);

        return metadataListType;
    }

    /**
     * @return string that contains the serialized values
     */
    private String buildValueString(final ITupleModel model, final IAxis axis) throws SensorException {
        if (model.size() == 0)
            return StringUtils.EMPTY;

        if (java.util.Date.class.isAssignableFrom(axis.getDataClass()))
            return buildStringDateAxis(model, axis);

        if (Number.class.isAssignableFrom(axis.getDataClass())
                || Boolean.class.isAssignableFrom(axis.getDataClass()))
            return buildStringNumberAxis(model, axis);

        if (String.class.isAssignableFrom(axis.getDataClass()))
            return buildStringAxis(model, axis);

        throw new IllegalArgumentException(Messages.getString("org.kalypso.ogc.sensor.zml.ZmlFactory.21")); //$NON-NLS-1$
    }

    private static String buildStringAxis(final ITupleModel model, final IAxis axis) throws SensorException {
        final StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < model.size(); i++) {
            buffer.append(model.get(i, axis)).append(";"); //$NON-NLS-1$
        }

        return StringUtils.chop(buffer.toString());
    }

    private String buildStringDateAxis(final ITupleModel model, final IAxis axis) throws SensorException {
        final StringBuilder buffer = new StringBuilder();
        final DateParser dateParser = getDateParser(m_timezone);

        for (int i = 0; i < model.size(); i++) {
            final Object obj = model.get(i, axis);
            buffer.append(dateParser.toString(obj)).append(";"); //$NON-NLS-1$
        }

        return StringUtils.chop(buffer.toString());
    }

    /**
     * Uses the default toString() method of the elements
     */
    private static String buildStringNumberAxis(final ITupleModel model, final IAxis axis) throws SensorException {
        final StringBuffer buffer = new StringBuffer();

        for (int i = 0; i < model.size(); i++) {
            final Object elt = model.get(i, axis);
            if (elt == null)
                LOG.warning(Messages.getString("org.kalypso.ogc.sensor.zml.ZmlFactory.24") + i //$NON-NLS-1$
                        + Messages.getString("org.kalypso.ogc.sensor.zml.ZmlFactory.25") + axis); //$NON-NLS-1$

            buffer.append(elt).append(";"); //$NON-NLS-1$
        }

        return StringUtils.chop(buffer.toString());
    }

    /**
     * Parser for the type <code>date</code>. It uses following format string:
     *
     * <pre>
     *      yyyy-MM-dd'T'HH:mm:ss
     * </pre>
     *
     * FIXME: we should use {@link javax.xml.bind.DatatypeConverter} instead
     */
    private static DateParser getDateParser(final TimeZone timezone) {
        final DateParser parser = new DateParser(XML_DATETIME_FORMAT);
        parser.setTimezone(timezone);

        return parser;
    }
}