Package org.apache.sis.storage.netcdf

Source Code of org.apache.sis.storage.netcdf.NetcdfStoreProvider

/*
* 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.storage.netcdf;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import org.apache.sis.internal.netcdf.Decoder;
import org.apache.sis.internal.netcdf.impl.ChannelDecoder;
import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
import org.apache.sis.internal.storage.ChannelDataInput;
import org.apache.sis.internal.system.SystemListener;
import org.apache.sis.internal.system.Modules;
import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreProvider;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.ProbeResult;
import org.apache.sis.util.logging.WarningListeners;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.Version;


/**
* The provider of {@link NetcdfStore} instances. Given a {@link StorageConnector} input,
* this class tries to instantiate a {@code NetcdfStore} using the embedded NetCDF decoder.
* If the embedded decoder can not decode the given input and the UCAR library is reachable
* on the classpath, then this class tries to instantiate a {@code NetcdfStore} backed by
* the UCAR library.
*
* {@section Thread safety}
* The same {@code NetcdfStoreProvider} instance can be safely used by many threads without synchronization on
* the part of the caller. However the {@link NetcdfStore} instances created by this factory are not thread-safe.
*
* @author  Martin Desruisseaux (Geomatys)
* @since   0.3
* @version 0.4
* @module
*
* @see NetcdfStore
*/
public class NetcdfStoreProvider extends DataStoreProvider {
    /**
     * The MIME type for NetCDF files.
     */
    static final String MIME_TYPE = "application/x-netcdf";

    /**
     * The name of the {@link ucar.nc2.NetcdfFile} class, which is {@value}.
     */
    private static final String UCAR_CLASSNAME = "ucar.nc2.NetcdfFile";

    /**
     * The {@link ucar.nc2.NetcdfFile} class, or {@code null} if not found. An attempt to load this class
     * will be performed when first needed since the UCAR library is optional. If not found, then this field
     * will be assigned the {@link Void#TYPE} sentinel value, meaning "No UCAR library on the classpath".
     */
    private static Class<?> netcdfFileClass;

    /**
     * If the {@link #netcdfFileClass} has been found, then the {@link ucar.nc2.NetcdfFile#canOpen(String)}
     * static method.
     */
    private static volatile Method canOpenFromPath;

    /**
     * If the {@link #netcdfFileClass} has been found, then the {@link DecoderWrapper} constructor receiving
     * in argument the name of the NetCDF file as a {@link String} object. Otherwise {@code null}.
     */
    private static volatile Constructor<? extends Decoder> createFromPath;

    /**
     * If the {@link #netcdfFileClass} has been found, then the {@link DecoderWrapper} constructor receiving
     * in argument a UCAR {@code NetcdfFile} object. Otherwise {@code null}.
     */
    private static volatile Constructor<? extends Decoder> createFromUCAR;

    /**
     * Clears the cached constructors if the classpath has changed,
     * because the UCAR library may no longer be on the classpath.
     */
    static {
        SystemListener.add(new SystemListener(Modules.NETCDF) {
            @Override protected void classpathChanged() {
                reset();
            }
        });
    }

    /**
     * Creates a new provider.
     */
    public NetcdfStoreProvider() {
    }

    /**
     * Returns {@link ProbeResult#SUPPORTED} if the given storage appears to be supported by {@link NetcdfStore}.
     * Returning {@code SUPPORTED} from this method does not guarantee that reading or writing will succeed,
     * only that there appears to be a reasonable chance of success based on a brief inspection of the
     * {@linkplain StorageConnector#getStorage() storage object} or contents.
     *
     * @param  storage Information about the storage (URL, stream, {@link ucar.nc2.NetcdfFile} instance, <i>etc</i>).
     * @return {@code SUPPORTED} if the given storage seems to be usable by the {@code NetcdfStore} instances.
     * @throws DataStoreException if an I/O error occurred.
     */
    @Override
    public ProbeResult probeContent(StorageConnector storage) throws DataStoreException {
        int     version     = 0;
        boolean hasVersion  = false;
        boolean isSupported = false;
        final ByteBuffer buffer = storage.getStorageAs(ByteBuffer.class);
        if (buffer != null) {
            if (buffer.remaining() < Integer.SIZE / Byte.SIZE) {
                return ProbeResult.INSUFFICIENT_BYTES;
            }
            final int header = buffer.getInt(buffer.position());
            if ((header & 0xFFFFFF00) == ChannelDecoder.MAGIC_NUMBER) {
                hasVersion  = true;
                version     = header & 0xFF;
                isSupported = (version >= 1 && version <= ChannelDecoder.MAX_VERSION);
            }
        }
        /*
         * If we failed to check using the embedded decoder, tries using the UCAR library.
         * The UCAR library is an optional dependency. If that library is present and the
         * input is a String, then the following code may trigs a large amount of classes
         * loading.
         *
         * Note that the UCAR library expects a String argument, not a File, because it
         * has special cases for "file:", "http:", "nodods:" and "slurp:" protocols.
         */
        if (!isSupported) {
            final String path = storage.getStorageAs(String.class);
            if (path != null) {
                ensureInitialized();
                final Method method = canOpenFromPath;
                if (method != null) try {
                    isSupported = (Boolean) method.invoke(null, path);
                } catch (IllegalAccessException e) {
                    // Should never happen, since the method is public.
                    throw (Error) new IncompatibleClassChangeError("canOpen").initCause(e);
                } catch (InvocationTargetException e) {
                    final Throwable cause = e.getCause();
                    if (cause instanceof DataStoreException) throw (DataStoreException) cause;
                    if (cause instanceof RuntimeException)   throw (RuntimeException)   cause;
                    if (cause instanceof Error)              throw (Error)              cause;
                    if (cause instanceof FileNotFoundException) {
                        /*
                         * Happen if the String argument uses any protocol not recognized by the UCAR library,
                         * in which case UCAR tries to open it as a file even if it is not a file. For example
                         * we get this exception for "jar:file:/file.jar!/entry.nc".
                         */
                        Logging.recoverableException(
                                Logging.getLogger("org.apache.sis.storage"), netcdfFileClass, "canOpen", cause);
                        return ProbeResult.UNSUPPORTED_STORAGE;
                    }
                    throw new DataStoreException(e); // The cause may be IOException.
                }
            } else {
                /*
                 * Check if the given input is itself an instance of the UCAR oject.
                 * We check classnames instead of netcdfFileClass.isInstance(storage)
                 * in order to avoid loading the UCAR library if not needed.
                 */
                for (Class<?> type = storage.getStorage().getClass(); type != null; type = type.getSuperclass()) {
                    if (UCAR_CLASSNAME.equals(type.getName())) {
                        isSupported = true;
                        break;
                    }
                }
            }
        }
        /*
         * At this point, the readability status has been determined. The file version number
         * is unknown if we are able to open the file only through the UCAR library.
         */
        if (hasVersion) {
            return new ProbeResult(isSupported, MIME_TYPE, Version.valueOf(version));
        }
        return isSupported ? new ProbeResult(true, MIME_TYPE, null) : ProbeResult.UNSUPPORTED_STORAGE;
    }

    /**
     * Returns a {@link NetcdfStore} implementation associated with this provider.
     *
     * @param  storage Information about the storage (URL, stream, {@link ucar.nc2.NetcdfFile} instance, <i>etc</i>).
     * @return A data store implementation associated with this provider for the given storage.
     * @throws DataStoreException If an error occurred while creating the data store instance.
     */
    @Override
    public DataStore open(final StorageConnector storage) throws DataStoreException {
        return new NetcdfStore(storage);
    }

    /**
     * Creates a decoder for the given input. This method invokes
     * {@link StorageConnector#closeAllExcept(Object)} after the decoder has been created.
     *
     * @param  listeners Where to send the warnings.
     * @param  storage Information about the input (file, input stream, <i>etc.</i>)
     * @return The decoder for the given input.
     * @throws IOException If an error occurred while opening the NetCDF file.
     * @throws DataStoreException If a logical error (other than I/O) occurred.
     */
    static Decoder decoder(final WarningListeners<?> listeners, final StorageConnector storage)
            throws IOException, DataStoreException
    {
        Decoder decoder;
        Object keepOpen;
        final ChannelDataInput input = storage.getStorageAs(ChannelDataInput.class);
        if (input != null) try {
            decoder = new ChannelDecoder(listeners, input);
            keepOpen = input;
        } catch (DataStoreException e) {
            final String path = storage.getStorageAs(String.class);
            if (path != null) {
                decoder = createByReflection(listeners, path, false);
                keepOpen = path;
            }
            throw e;
        } else {
            keepOpen = storage.getStorage();
            decoder = createByReflection(listeners, keepOpen, true);
        }
        storage.closeAllExcept(keepOpen);
        return decoder;
    }

    /**
     * Creates a new NetCDF decoder as a wrapper around the UCAR library. This decoder is used only when we can
     * not create our embedded NetCDF decoder. This method uses reflection for creating the wrapper, in order
     * to keep the UCAR dependency optional.
     *
     * @param  listeners Where to send the warnings.
     * @param  input  The NetCDF file object of filename string from which to read data.
     * @param  isUCAR {@code true} if {@code input} is an instance of the UCAR {@link ucar.nc2.NetcdfFile} object,
     *                or {@code false} if it is the filename as a {@code String}.
     * @return The {@link DecoderWrapper} instance for the given input.
     * @throws IOException If an error occurred while opening the NetCDF file.
     * @throws DataStoreException If a logical error (other than I/O) occurred.
     */
    private static Decoder createByReflection(final WarningListeners<?> listeners, final Object input, final boolean isUCAR)
            throws IOException, DataStoreException
    {
        ensureInitialized();
        /*
         * Get the appropriate constructor for the isUCAR argument. This constructor will be null
         * if the above code failed to load the UCAR library. Otherwise, instantiate the wrapper.
         */
        final Constructor<? extends Decoder> constructor;
        final Class<?> expectedType;
        if (isUCAR) {
            constructor  = createFromUCAR;
            expectedType = netcdfFileClass;
        } else {
            constructor  = createFromPath;
            expectedType = String.class;
        }
        if (constructor == null || !expectedType.isInstance(input)) {
            return null;
        }
        try {
            return constructor.newInstance(listeners, input);
        } catch (InvocationTargetException e) {
            final Throwable cause = e.getCause();
            if (cause instanceof IOException)        throw (IOException)        cause;
            if (cause instanceof DataStoreException) throw (DataStoreException) cause;
            if (cause instanceof RuntimeException)   throw (RuntimeException)   cause;
            if (cause instanceof Error)              throw (Error)              cause;
            throw new UndeclaredThrowableException(cause); // Should never happen actually.
        } catch (Exception e) { // (ReflectiveOperationException) on JDK7 branch.
            throw new AssertionError(e); // Should never happen (shall be verified by the JUnit tests).
        }
    }

    /**
     * Gets the {@link java.lang.Class} that represent the {@link ucar.nc2.NetcdfFile type}.
     */
    private static void ensureInitialized() {
        if (netcdfFileClass == null) {
            synchronized (NetcdfStoreProvider.class) {
                /*
                 * No double-check because it is not a big deal if the constructors are fetched twice.
                 * The sychronization is mostly a safety against concurrent execution of 'reset()'.
                 */
                try {
                    netcdfFileClass = Class.forName(UCAR_CLASSNAME);
                } catch (ClassNotFoundException e) {
                    netcdfFileClass = Void.TYPE;
                    return;
                }
                try {
                    /*
                     * UCAR API.
                     */
                    canOpenFromPath = netcdfFileClass.getMethod("canOpen", String.class);
                    assert canOpenFromPath.getReturnType() == Boolean.TYPE;
                    /*
                     * SIS Wrapper API.
                     */
                    final Class<? extends Decoder> wrapper =
                            Class.forName("org.apache.sis.internal.netcdf.ucar.DecoderWrapper").asSubclass(Decoder.class);
                    final Class<?>[] parameterTypes = new Class<?>[] {WarningListeners.class, netcdfFileClass};
                    createFromUCAR = wrapper.getConstructor(parameterTypes);
                    parameterTypes[1] = String.class;
                    createFromPath = wrapper.getConstructor(parameterTypes);
                } catch (Exception e) { // (ReflectiveOperationException) on JDK7 branch.
                    throw new AssertionError(e); // Should never happen (shall be verified by the JUnit tests).
                }
            }
        }
    }

    /**
     * Invoked when the classpath changed. Clears the cached class and constructors, since we don't know
     * if the UCAR library is still on the classpath.
     */
    static synchronized void reset() {
        netcdfFileClass = null;
        canOpenFromPath = null;
        createFromUCAR  = null;
        createFromPath  = null;
    }
}
TOP

Related Classes of org.apache.sis.storage.netcdf.NetcdfStoreProvider

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.