Package org.apache.sis.xml

Source Code of org.apache.sis.xml.Pooled

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.xml;

import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.ConcurrentModificationException;
import java.util.Locale;
import java.util.TimeZone;
import javax.xml.validation.Schema;
import javax.xml.bind.Marshaller;
import javax.xml.bind.JAXBException;
import javax.xml.bind.PropertyException;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.apache.sis.util.Locales;
import org.apache.sis.util.Version;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.WarningListener;
import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.internal.jaxb.Context;
import org.apache.sis.internal.jaxb.LegacyNamespaces;


/**
* Base class of {@link PooledMarshaller} and {@link PooledUnmarshaller}.
* This class provides basic service for saving the initial values of (un)marshaller properties,
* in order to reset them to their initial values after usage. This is required in order to allow
* (un)marshaller reuse. In addition this base class translates properties key from JDK 6 names to
* "endorsed JAR" names if needed.
*
* @author  Martin Desruisseaux (Geomatys)
* @since   0.3 (derived from geotk-3.00)
* @version 0.3
* @module
*/
abstract class Pooled {
    /**
     * The keys of entries that can be stored in the {@link #schemas} map.
     * Those keys are documented in {@link XML#SCHEMAS}.
     */
    private static final String[] SCHEMA_KEYS = {"gmd"};

    /**
     * The prefix of property names which are provided in external (endorsed) implementation of JAXB.
     * This is slightly different than the prefix used by the implementation bundled with the JDK 6,
     * which is {@code "com.sun.xml.internal.bind"}.
     *
     * @see #convertPropertyKey(String)
     */
    static final String ENDORSED_PREFIX = "com.sun.xml.bind.";

    /**
     * {@code true} if the JAXB implementation is the one bundled in JDK 6, or {@code false}
     * if this is the external implementation provided as a JAR file in the endorsed directory.
     * If {@code true}, then an additional {@code "internal"} package name needs to be inserted
     * in the property keys.
     *
     * @see #convertPropertyKey(String)
     */
    private final boolean internal;

    /**
     * The initial state of the (un)marshaller. Will be filled only as needed,
     * often with null values (which must be supported by the map implementation).
     *
     * <ul>
     *   <li>For each entry having a key of type {@link Class}, the value is the argument
     *       to be given to a {@code marshaller.setFoo(value)} method.</li>
     *   <li>For each entry having a key of type {@link String}, the value is the argument
     *       to be given to the {@code marshaller.setProperty(key, value)} method.</li>
     * </ul>
     *
     * This map is never {@code null}.
     */
    final Map<Object,Object> initialProperties;

    /**
     * Bit masks for various boolean attributes. This include whatever the language codes
     * or the country codes should be substituted by a simpler character string elements.
     * Those bits are determined by the {@link XML#STRING_SUBSTITUTES} property.
     */
    private int bitMasks;

    /**
     * An optional locale for {@link org.opengis.util.InternationalString} and
     * {@link org.opengis.util.CodeList}. Can be set by the {@link XML#LOCALE} property.
     */
    private Locale locale;

    /**
     * The timezone, or {@code null} if unspecified.
     *  Can be set by the {@link XML#TIMEZONE} property.
     */
    private TimeZone timezone;

    /**
     * The base URL of ISO 19139 (or other standards) schemas. It shall be an unmodifiable
     * instance because {@link #getProperty(String)} returns a direct reference to the user.
     * The valid values are documented in the {@link XML#SCHEMAS} property.
     */
    private Map<String,String> schemas;

    /**
     * Whether {@link FilteredNamespaces} shall be used of not. Values can be:
     *
     * <ul>
     *   <li>0 for the default behavior, which applies namespace replacements only if the {@link XML#GML_VERSION}
     *       property is set to an older value than the one supported natively by SIS.</li>
     *   <li>1 for forcing namespace replacements at unmarshalling time. This is useful for reading a XML document
     *       of unknown GML version.</li>
     *   <li>2 for disabling namespace replacements. XML (un)marshalling will use the namespaces URI supported
     *       natively by SIS as declared in JAXB annotations. This is sometime useful for debugging purpose.</li>
     * </ul>
     *
     * @see LegacyNamespaces#APPLY_NAMESPACE_REPLACEMENTS
     */
    private byte xmlnsReplaceCode;

    /**
     * The GML version to be marshalled or unmarshalled, or {@code null} if unspecified.
     * If null, then the latest version is assumed.
     *
     * @see #getGMLVersion()
     */
    private Version gmlVersion;

    /**
     * The reference resolver to use during unmarshalling.
     * Can be set by the {@link XML#RESOLVER} property.
     */
    private ReferenceResolver resolver;

    /**
     * The object converters to use during (un)marshalling.
     * Can be set by the {@link XML#CONVERTER} property.
     */
    private ValueConverter converter;

    /**
     * The object to inform about warnings, or {@code null} if none.
     */
    private WarningListener<?> warningListener;

    /**
     * The {@link System#nanoTime()} value of the last call to {@link #reset(Pooled)}.
     * This is used for disposing (un)marshallers that have not been used for a while,
     * since {@code reset()} is invoked just before to push a (un)marshaller in the pool.
     */
    volatile long resetTime;

    /**
     * Creates a {@link PooledTemplate}.
     *
     * @param internal {@code true} if the JAXB implementation is the one bundled in JDK 6,
     *        or {@code false} if this is the external implementation provided as a JAR file
     *        in the endorsed directory.
     */
    Pooled(final boolean internal) {
        this.internal = internal;
        initialProperties = new LinkedHashMap<Object,Object>();
    }

    /**
     * Creates a {@link PooledMarshaller} or {@link PooledUnmarshaller}. The {@link #initialize(Pooled)}
     * method must be invoked after this constructor for completing the initialization.
     *
     * @param template The {@link PooledTemplate} from which to get the initial values.
     */
    Pooled(final Pooled template) {
        initialProperties = new LinkedHashMap<Object,Object>();
        internal = template.internal;
    }

    /**
     * Completes the creation of a {@link PooledMarshaller} or {@link PooledUnmarshaller}.
     * This method is not invoked in the {@link #Pooled(Pooled)} constructor in order to
     * give to subclasses a chance to complete their construction first.
     *
     * @param  template The {@link PooledTemplate} from which to get the initial values.
     * @throws JAXBException If an error occurred while setting a property.
     */
    final void initialize(final Pooled template) throws JAXBException {
        reset(template); // Set the SIS properties first. JAXB properties are set below.
        for (final Map.Entry<Object,Object> entry : template.initialProperties.entrySet()) {
            setStandardProperty((String) entry.getKey(), entry.getValue());
        }
    }

    /**
     * Releases resources and resets the (un)marshaller to its initial state.
     * This method is invoked by {@link MarshallerPool} just before to push a
     * (un)marshaller in the pool after its usage.
     *
     * @param  template The {@link PooledTemplate} from which to get the initial values.
     * @throws JAXBException If an error occurred while restoring a property.
     */
    public final void reset(final Pooled template) throws JAXBException {
        for (final Map.Entry<Object,Object> entry : initialProperties.entrySet()) {
            reset(entry.getKey(), entry.getValue());
        }
        initialProperties.clear();
        bitMasks         = template.bitMasks;
        locale           = template.locale;
        timezone         = template.timezone;
        schemas          = template.schemas;
        xmlnsReplaceCode = template.xmlnsReplaceCode;
        gmlVersion       = template.gmlVersion;
        resolver         = template.resolver;
        converter        = template.converter;
        warningListener  = template.warningListener;
        resetTime        = System.nanoTime();
        if (this instanceof Marshaller) {
            bitMasks |= Context.MARSHALLING;
        }
    }

    /**
     * Resets the given marshaller property to its initial state. This method is invoked
     * automatically by the {@link #reset(Pooled)} method. The key is either a {@link String}
     * or a {@link Class}. If this is a string, then the value shall be given to the
     * {@code setProperty(key, value)} method. Otherwise the value shall be given to
     * {@code setFoo(value)} method where {@code "Foo"} is determined from the key.
     *
     * @param  key   The property to reset.
     * @param  value The initial value to give to the property.
     * @throws JAXBException If an error occurred while restoring a property.
     */
    protected abstract void reset(final Object key, final Object value) throws JAXBException;

    /**
     * Returns the {@code FilterVersion} enumeration value to use for the current GML version, or
     * {@code null} if the SIS native version is suitable. If this method returns a non-null value,
     * then the output generated by JAXB will need to go through a {@link FilteredStreamWriter}
     * in order to replace the namespace of the GML version implemented by SIS by the namespace of
     * the GML version asked by the user.
     *
     * @see FilteredNamespaces
     */
    final FilterVersion getFilterVersion() {
        switch (xmlnsReplaceCode) {
            case 0: {
                // Apply namespace replacements only for older versions than the one supported natively by SIS.
                if (gmlVersion != null) {
                    if (gmlVersion.compareTo(LegacyNamespaces.VERSION_3_2_1, 2) < 0) {
                        return FilterVersion.GML31;
                    }
                }
                break;
            }
            case 1: {
                // Force namespace replacements at unmarshalling time (illegal for marshalling).
                if ((bitMasks & Context.MARSHALLING) == 0) {
                    return FilterVersion.ALL;
                }
                break;
            }
            // case 2: disable namespace replacements.
        }
        return null;
    }

    /**
     * Returns {@code true} if the initial property is already saved for the given key.
     * Note that a property set to {@code null} is still considered as defined.
     */
    final boolean isPropertySaved(final Class<?> key) {
        return initialProperties.containsKey(key);
    }

    /**
     * Saves the current value of a property. This method is invoked before a value is
     * modified for the first time, in order to allow {@link #reset(Pooled)} to restore
     * the (un)marshaller to its initial state.
     *
     * @param type  The property to save.
     * @param value The current value of the property.
     */
    final <E> void saveProperty(final Class<E> type, final E value) {
        if (initialProperties.put(type, value) != null) {
            // Should never happen, unless on concurrent changes in a backgroung thread.
            throw new ConcurrentModificationException(Errors.format(Errors.Keys.UnexpectedChange_1,
                    type.getInterfaces()[0].getSimpleName() + ".get" + type.getSimpleName()));
        }
    }

    /**
     * Converts a property key from the JAXB name to the underlying implementation name.
     * This applies only to property keys in the {@code "com.sun.xml.bind"} namespace.
     *
     * @param  key The JAXB property key.
     * @return The property key to use.
     */
    private String convertPropertyKey(String key) {
        if (internal && key.startsWith(ENDORSED_PREFIX)) {
            final StringBuilder buffer = new StringBuilder(key.length() + 10);
            key = buffer.append("com.sun.xml.internal.bind.")
                    .append(key, ENDORSED_PREFIX.length(), key.length()).toString();
        }
        return key;
    }

    /**
     * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
     * It saves the initial state if it was not already done, but subclasses will
     * need to complete the work.
     */
    public final void setProperty(String name, final Object value) throws PropertyException {
        try {
            /* switch (name) */ {
                if (name.equals(XML.LOCALE)) {
                    locale = (value instanceof CharSequence) ? Locales.parse(value.toString()) : (Locale) value;
                    return;
                }
                if (name.equals(XML.TIMEZONE)) {
                    timezone = (value instanceof CharSequence) ? TimeZone.getTimeZone(value.toString()) : (TimeZone) value;
                    return;
                }
                if (name.equals(XML.SCHEMAS)) {
                    final Map<?,?> map = (Map<?,?>) value;
                    Map<String,String> copy = null;
                    if (map != null) {
                        copy = new HashMap<String,String>(4);
                        for (final String key : SCHEMA_KEYS) {
                            final Object schema = map.get(key);
                            if (schema != null) {
                                if (!(schema instanceof String)) {
                                    throw new PropertyException(Errors.format(Errors.Keys.IllegalPropertyClass_2,
                                            name + "[\"" + key + "\"]", value.getClass()));
                                }
                                copy.put(key, (String) schema);
                            }
                        }
                        copy = CollectionsExt.unmodifiableOrCopy(copy);
                    }
                    schemas = copy;
                    return;
                }
                if (name.equals(XML.GML_VERSION)) {
                    gmlVersion = (value instanceof CharSequence) ? new Version(value.toString()) : (Version) value;
                    return;
                }
                if (name.equals(XML.RESOLVER)) {
                    resolver = (ReferenceResolver) value;
                    return;
                }
                if (name.equals(XML.CONVERTER)) {
                    converter = (ValueConverter) value;
                    return;
                }
                if (name.equals(XML.STRING_SUBSTITUTES)) {
                    bitMasks &= ~(Context.SUBSTITUTE_LANGUAGE |
                                  Context.SUBSTITUTE_COUNTRY  |
                                  Context.SUBSTITUTE_FILENAME |
                                  Context.SUBSTITUTE_MIMETYPE);
                    if (value != null) {
                        for (final CharSequence substitute : (CharSequence[]) value) {
                            if (CharSequences.equalsIgnoreCase(substitute, "language")) {
                                bitMasks |= Context.SUBSTITUTE_LANGUAGE;
                            } else if (CharSequences.equalsIgnoreCase(substitute, "country")) {
                                bitMasks |= Context.SUBSTITUTE_COUNTRY;
                            } else if (CharSequences.equalsIgnoreCase(substitute, "filename")) {
                                bitMasks |= Context.SUBSTITUTE_FILENAME;
                            } else if (CharSequences.equalsIgnoreCase(substitute, "mimetype")) {
                                bitMasks |= Context.SUBSTITUTE_MIMETYPE;
                            }
                        }
                    }
                    return;
                }
                if (name.equals(XML.WARNING_LISTENER)) {
                    warningListener = (WarningListener<?>) value;
                    return;
                }
                if (name.equals(LegacyNamespaces.APPLY_NAMESPACE_REPLACEMENTS)) {
                    xmlnsReplaceCode = 0;
                    if (value != null) {
                        xmlnsReplaceCode = ((Boolean) value) ? (byte) 1 : (byte) 2;
                    }
                    return;
                }
            }
        } catch (RuntimeException e) { // (ClassCastException | IllformedLocaleException) on the JDK7 branch.
            throw new PropertyException(Errors.format(
                    Errors.Keys.IllegalPropertyClass_2, name, value.getClass()), e);
        }
        /*
         * If we reach this point, the given name is not a SIS property. Try to handle
         * it as a (un)marshaller-specific property, after saving the previous value.
         */
        name = convertPropertyKey(name);
        if (!initialProperties.containsKey(name)) {
            if (initialProperties.put(name, getStandardProperty(name)) != null) {
                // Should never happen, unless on concurrent changes in a backgroung thread.
                throw new ConcurrentModificationException(name);
            }
        }
        setStandardProperty(name, value);
    }

    /**
     * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
     */
    public final Object getProperty(final String name) throws PropertyException {
        /*switch (name)*/ {
            if (name.equals(XML.LOCALE))           return locale;
            if (name.equals(XML.TIMEZONE))         return timezone;
            if (name.equals(XML.SCHEMAS))          return schemas;
            if (name.equals(XML.GML_VERSION))      return gmlVersion;
            if (name.equals(XML.RESOLVER))         return resolver;
            if (name.equals(XML.CONVERTER))        return converter;
            if (name.equals(XML.WARNING_LISTENER)) return warningListener;
            if (name.equals(XML.STRING_SUBSTITUTES)) {
                int n = 0;
                final String[] substitutes = new String[4];
                if ((bitMasks & Context.SUBSTITUTE_LANGUAGE) != 0) substitutes[n++] = "language";
                if ((bitMasks & Context.SUBSTITUTE_COUNTRY!= 0) substitutes[n++] = "country";
                if ((bitMasks & Context.SUBSTITUTE_FILENAME) != 0) substitutes[n++] = "filename";
                if ((bitMasks & Context.SUBSTITUTE_MIMETYPE) != 0) substitutes[n++] = "mimetype";
                return (n != 0) ? ArraysExt.resize(substitutes, n) : null;
            }
            if (name.equals(LegacyNamespaces.APPLY_NAMESPACE_REPLACEMENTS)) {
                switch (xmlnsReplaceCode) {
                    case 1return Boolean.TRUE;
                    case 2return Boolean.FALSE;
                    default: return null;
                }
            }
            return getStandardProperty(convertPropertyKey(name));
        }
    }

    /**
     * Sets the given property to the wrapped (un)marshaller. This method is invoked
     * automatically when the property given to the {@link #setProperty(String, Object)}
     * method was not one of the {@link XML} constants.
     */
    abstract void setStandardProperty(String name, Object value) throws PropertyException;

    /**
     * Gets the given property from the wrapped (un)marshaller. This method is invoked
     * automatically when the property key given to the {@link #getProperty(String)}
     * method was not one of the {@link XML} constants.
     */
    abstract Object getStandardProperty(String name) throws PropertyException;

    /**
     * Delegates to {@code setAdapter(adapter.getClass(), adapter)} as specified
     * in {@code [Un]Marshaller} javadoc.
     */
    @SuppressWarnings({"unchecked","rawtypes"})
    public final void setAdapter(final XmlAdapter adapter) {
        setAdapter((Class) adapter.getClass(), adapter);
    }

    /**
     * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
     * It saves the initial state if it was not already done, but subclasses will
     * need to complete the work.
     */
    @SuppressWarnings("rawtypes")
    public <A extends XmlAdapter> void setAdapter(final Class<A> type, final A adapter) {
        if (!isPropertySaved(type)) {
            saveProperty(type, getAdapter(type));
        }
    }

    /**
     * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
     */
    @SuppressWarnings("rawtypes")
    public abstract <A extends XmlAdapter> A getAdapter(final Class<A> type);

    /**
     * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
     * It saves the initial state if it was not already done, but subclasses will
     * need to complete the work.
     */
    public void setSchema(final Schema schema) {
        if (!isPropertySaved(Schema.class)) {
            saveProperty(Schema.class, getSchema());
        }
    }

    /**
     * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
     */
    public abstract Schema getSchema();

    /**
     * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
     * It saves the initial state if it was not already done, but subclasses will
     * need to complete the work.
     */
    public void setEventHandler(final ValidationEventHandler handler) throws JAXBException {
        if (!initialProperties.containsKey(ValidationEventHandler.class)) {
            saveProperty(ValidationEventHandler.class, getEventHandler());
        }
    }

    /**
     * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
     */
    public abstract ValidationEventHandler getEventHandler() throws JAXBException;

    /**
     * Must be invoked by subclasses before a {@code try} block performing a (un)marshalling
     * operation. Must be followed by a call to {@code finish()} in a {@code finally} block.
     *
     * {@preformat java
     *     Context context = begin();
     *     try {
     *         ...
     *     } finally {
     *         context.finish();
     *     }
     * }
     *
     * @see Context#finish();
     */
    final Context begin() {
        return new Context(bitMasks, locale, timezone, schemas, gmlVersion, resolver, converter, warningListener);
    }
}
TOP

Related Classes of org.apache.sis.xml.Pooled

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.