Author: desruisseaux
Date: Fri May 31 10:53:16 2013
New Revision: 1488146

URL: http://svn.apache.org/r1488146
Log:
More documentation, and anticipate the JDBC case.

Modified:
    
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreConnection.java

Modified: 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreConnection.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreConnection.java?rev=1488146&r1=1488145&r2=1488146&view=diff
==============================================================================
--- 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreConnection.java
 [UTF-8] (original)
+++ 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreConnection.java
 [UTF-8] Fri May 31 10:53:16 2013
@@ -21,11 +21,16 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.nio.ByteBuffer;
 import java.nio.channels.ReadableByteChannel;
+import javax.imageio.ImageIO;
 import javax.imageio.stream.ImageInputStream;
-import org.apache.sis.internal.storage.ChannelImageInputStream;
-import org.apache.sis.internal.storage.IOUtilities;
+import java.sql.Connection;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+import org.apache.sis.util.Classes;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.storage.IOUtilities;
+import org.apache.sis.internal.storage.ChannelImageInputStream;
 
 
 /**
@@ -35,11 +40,23 @@ import org.apache.sis.util.resources.Err
  * <ul>
  *   <li>A {@link java.nio.file.Path} or a {@link java.io.File} or file or a 
directory in a file system.</li>
  *   <li>A {@link java.net.URI} or a {@link java.net.URL} to a distant 
resource.</li>
+ *   <li>A {@link CharSequence} interpreted as a filename or a URL.</li>
  *   <li>A {@link java.nio.channels.Channel} or a {@link DataInput}.</li>
- *   <li>A {@link javax.sql.DataSource} or a {@link java.sql.Connection} to a 
JDBC database.</li>
+ *   <li>A {@link DataSource} or a {@link Connection} to a JDBC database.</li>
  *   <li>Any other {@code DataStore}-specific object, for example {@link 
ucar.nc2.NetcdfFile}.</li>
  * </ul>
  *
+ * This class is used only for discovery of a {@code DataStore} implementation 
capable to handle the input.
+ * Once a suitable {@code DataStore} has been found, the {@code 
DataStoreConnection} instance is typically
+ * discarded since each data store implementation will use their own 
input/output objects.
+ *
+ * <p>This class does not implement {@link AutoCloseable} on intend, because 
the connection shall not be closed
+ * if it has been taken by a {@link DataStore} instance. The connection shall 
be closed only if no suitable
+ * {@code DataStore} has been found.</p>
+ *
+ * <p>Instances of this class are serializable if the {@code storage} object 
given at construction time
+ * is serializable.</p>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
  * @version 0.3
@@ -47,12 +64,19 @@ import org.apache.sis.util.resources.Err
  */
 public class DataStoreConnection implements Serializable {
     /**
-     * The input given at construction time.
+     * For cross-version compatibility.
      */
-    private final Object input;
+    private static final long serialVersionUID = 2524083964906593093L;
 
     /**
-     * A name for the input, or {@code null} if none.
+     * The input/output object given at construction time.
+     *
+     * @see #getStorage()
+     */
+    private final Object storage;
+
+    /**
+     * A name for the input/output object, or {@code null} if none.
      * This field is initialized only when first needed.
      */
     private transient String name;
@@ -64,27 +88,27 @@ public class DataStoreConnection impleme
     private transient String extension;
 
     /**
+     * A read-only view of the buffer over the first bytes of the stream, or 
{@code null} if none.
+     * This field is initialized together with {@link #asDataInput} when first 
needed.
+     *
+     * @see #asByteBuffer()
+     */
+    private transient ByteBuffer asByteBuffer;
+
+    /**
      * The input as a data input stream, or {@code null} if none.
      * This field is initialized together with {@link #asByteBuffer} when 
first needed.
      *
-     * <p>Unless the {@link #input} is already an instance of {@link 
DataInput}, this field will be given an instance
-     * of {@link ChannelImageInputStream}, not an arbitrary stream.  In 
particular, we do <strong>not</strong> invoke
-     * the {@link javax.imageio.ImageIO#createImageInputStream(Object)} 
factory method because some SIS data stores
-     * will want to access the channel and buffer directly.</p>
+     * <p>Unless the {@link #storage} is already an instance of {@link 
DataInput}, this field will be
+     * given an instance of {@link ChannelImageInputStream} if possible rather 
than an arbitrary stream.
+     * In particular, we invoke the {@link 
ImageIO#createImageInputStream(Object)} factory method only in
+     * last resort because some SIS data stores will want to access the 
channel and buffer directly.</p>
      *
      * @see #asDataInput()
      */
     private transient DataInput asDataInput;
 
     /**
-     * A read-only view of the buffer over the first bytes of the stream, or 
{@code null} if none.
-     * This field is initialized together with {@link #asDataInput} when first 
needed.
-     *
-     * @see #asByteBuffer()
-     */
-    private transient ByteBuffer asByteBuffer;
-
-    /**
      * If {@link #asDataInput} is an instance of {@link ImageInputStream}, 
then the stream position
      * at the time the {@code asDataInput} field has been initialized. This is 
often zero.
      */
@@ -96,78 +120,118 @@ public class DataStoreConnection impleme
     private transient boolean isInitialized;
 
     /**
-     * Creates a new data store connection wrapping the given input object.
-     * The input can be of any type, but the class javadoc lists the most 
typical ones.
+     * The input/output object as a JDBC connection.
+     *
+     * @see #asDatabase()
+     */
+    private transient Connection asDatabase;
+
+    /**
+     * Creates a new data store connection wrapping the given input/output 
object.
+     * The object can be of any type, but the class javadoc lists the most 
typical ones.
      *
-     * @param input The input as a URL, file, image input stream, <i>etc.</i>.
+     * @param storage The input/output object as a URL, file, image input 
stream, <i>etc.</i>.
      */
-    public DataStoreConnection(final Object input) {
-        ArgumentChecks.ensureNonNull("input", input);
-        this.input = input;
+    public DataStoreConnection(final Object storage) {
+        ArgumentChecks.ensureNonNull("storage", storage);
+        this.storage = storage;
     }
 
     /**
-     * Returns the input object given at construction time.
-     * The input can be of any type, but the class javadoc lists the most 
typical ones.
+     * Returns the input/output object given at construction time.
+     * The object can be of any type, but the class javadoc lists the most 
typical ones.
      *
-     * @return The input as a URL, file, image input stream, <i>etc.</i>.
+     * @return The input/output object as a URL, file, image input stream, 
<i>etc.</i>.
      */
-    public Object getInput() {
-        return input;
+    public Object getStorage() {
+        return storage;
     }
 
     /**
-     * Returns the input name, or the {@linkplain Class#getSimpleName() simple 
class name} if this method can not infer
-     * a name from the input. If the input is a {@link java.nio.file.Path}, 
{@link java.io.File}, {@link java.net.URL}
-     * or {@link java.net.URI}, then the default implementation uses dedicated 
API like {@link Path#getFileName()}.
-     * If the input is a {@link CharSequence}, then the default implementation 
gets a string representation of
-     * the input and returns the part after the last {@code '/'} or 
platform-dependent name separator character,
-     * if any.
+     * Returns a short name of the input/output object. The default 
implementation performs
+     * the following choices based on the type of the {@linkplain 
#getStorage() storage} object:
      *
-     * @return The input name, or simple class name if the input is an object 
of unknown type.
+     * <ul>
+     *   <li>For {@link java.nio.file.Path}, {@link java.io.File}, {@link 
java.net.URI} or {@link java.net.URL}
+     *       instances, this method uses dedicated API like {@link 
Path#getFileName()}.</li>
+     *   <li>For {@link CharSequence} instances, this method gets a string 
representation of the storage object
+     *       and returns the part after the last {@code '/'} character or 
platform-dependent name separator.</li>
+     *   <li>For instances of unknown type, this method builds a string 
representation using the class name.
+     *       Note that the string representation of unknown types may change 
in any future SIS version.</li>
+     * </ul>
+     *
+     * @return A short name of the storage object.
      */
-    public String getInputName() {
+    public String getStorageName() {
         if (name == null) {
-            name = IOUtilities.filename(input);
+            name = IOUtilities.filename(storage);
             if (name == null) {
-                name = input.getClass().getSimpleName();
+                name = Classes.getShortClassName(storage);
             }
         }
         return name;
     }
 
     /**
-     * Returns the filename extension of the input. The default implementation 
recognizes the same input types
-     * than the {@link #getInputName()} method. If no extension is found, this 
method returns an empty string.
-     * If the input is an object of unknown type, this method return {@code 
null}.
+     * Returns the filename extension of the input/output object. The default 
implementation performs
+     * the following choices based on the type of the {@linkplain 
#getStorage() storage} object:
+     *
+     * <ul>
+     *   <li>For {@link java.nio.file.Path}, {@link java.io.File}, {@link 
java.net.URI}, {@link java.net.URL} or
+     *       {@link CharSequence} instances, this method returns the string 
after the last {@code '.'} character
+     *       in the filename, provided that the {@code '.'} is not the first 
filename character. This may be an
+     *       empty string if the filename has no extension, but never {@code 
null}.</li>
+     *   <li>For instances of unknown type, this method returns {@code 
null}.</li>
+     * </ul>
      *
-     * @return The extension, or an empty string if none, or {@code null} if 
the input is an object of unknown type.
+     * @return The filename extension, or an empty string if none, or {@code 
null} if the storage
+     *         is an object of unknown type.
      */
     public String getFileExtension() {
         if (extension == null) {
-            extension = IOUtilities.extension(input);
+            extension = IOUtilities.extension(storage);
         }
         return extension;
     }
 
     /**
+     * Returns a read-only view of the first bytes of the input stream, or 
{@code null} if unavailable.
+     * If non-null, this buffer can be used for a quick check of file magic 
number.
+     *
+     * @return The first bytes in the stream (read-only), or {@code null} if 
unavailable.
+     * @throws IOException If an error occurred while opening a stream for the 
input.
+     */
+    public ByteBuffer asByteBuffer() throws IOException {
+        if (!isInitialized) {
+            initialize();
+        }
+        return asByteBuffer;
+    }
+
+    /**
      * Returns the input as a {@link DataInput} if possible, or {@code null} 
otherwise.
-     * The default implementation performs the following choice:
+     * The default implementation performs the following choice based on the 
type of the
+     * {@linkplain #getStorage() storage} object:
      *
      * <ul>
-     *   <li>If the input is already an instance of {@link DataInput}, then it 
is returned unchanged.
-     *       This include the {@link ImageInputStream} and {@link 
javax.imageio.stream.ImageOutputStream} cases,
-     *       which are {@code DataInput} sub-interfaces.</li>
-     *
-     *   <li>Otherwise if the input is an instance of one of the types 
enumerated in the class javadoc
-     *       (except {@code DataStore}-specific types), then an {@link 
ImageInputStream} is created when first needed
-     *       and returned. Multiple invocations of this method on the same 
{@code DataStoreConnection} instance will
-     *       return the same {@code ImageInputStream} instance.</li>
+     *   <li>If the storage is already an instance of {@link DataInput} 
(including the {@link ImageInputStream}
+     *       and {@link javax.imageio.stream.ImageOutputStream} types), then 
it is returned unchanged.</li>
+     *
+     *   <li>Otherwise if the input is an instance of {@link 
java.nio.file.Path}, {@link java.io.File},
+     *       {@link java.net.URI}, {@link java.net.URL}, {@link CharSequence}, 
{@link java.io.InputStream} or
+     *       {@link java.nio.channels.ReadableByteChannel}, then an {@link 
ImageInputStream} backed by a
+     *       {@link ByteBuffer} is created when first needed and returned.</li>
+     *
+     *   <li>Otherwise if {@link ImageIO#createImageInputStream(Object)} 
returns a non-null value,
+     *       then this value is cached and returned.</li>
      *
      *   <li>Otherwise this method returns {@code null}.</li>
      * </ul>
      *
-     * @return The input as a {@link DataInput}, or {@code null} if the input 
is an object of unknown type.
+     * Multiple invocations of this method on the same {@code 
DataStoreConnection} instance will return the same
+     * {@code ImageInputStream} instance.
+     *
+     * @return The input as a {@code DataInput}, or {@code null} if the input 
is an object of unknown type.
      * @throws IOException If an error occurred while opening a stream for the 
input.
      */
     public DataInput asDataInput() throws IOException {
@@ -178,39 +242,58 @@ public class DataStoreConnection impleme
     }
 
     /**
-     * Returns a read-only view of the buffer over the first bytes of the 
stream, or {@code null} if unavailable.
-     * If non-null, this buffer can be used for a quick check of file magic 
number.
+     * Returns the input as a connection to a JDBC database if possible, or 
{@code null} otherwise.
+     * The default implementation performs the following choice based on the 
type of the
+     * {@linkplain #getStorage() storage} object:
      *
-     * @return The first bytes in the stream (read-only), or {@code null} if 
unavailable.
-     * @throws IOException If an error occurred while opening a stream for the 
input.
+     * <ul>
+     *   <li>If the storage is already an instance of {@link Connection}, then 
it is returned unchanged.</li>
+     *
+     *   <li>Otherwise if the storage is an instance of {@link DataSource}, 
then a connection is obtained
+     *       when first needed and returned.</li>
+     *
+     *   <li>Otherwise this method returns {@code null}.</li>
+     * </ul>
+     *
+     * Multiple invocations of this method on the same {@code 
DataStoreConnection} instance will return the same
+     * {@code Connection} instance.
+     *
+     * @return The storage as a {@code Connection}, or {@code null} if the 
storage is an object of unknown type.
+     * @throws SQLException If an error occurred while opening a database 
connection for the storage.
      */
-    public ByteBuffer asByteBuffer() throws IOException {
-        if (!isInitialized) {
-            initialize();
+    public Connection asDatabase() throws SQLException {
+        if (asDatabase == null) {
+            if (storage instanceof Connection) {
+                asDatabase = (Connection) storage;
+            } else if (storage instanceof DataSource) {
+                asDatabase = ((DataSource) storage).getConnection();
+            }
         }
-        return asByteBuffer;
+        return asDatabase;
     }
 
     /**
-     * Initializes the {@link #asDataInput} and {@link #asByteBuffer} fields.
+     * Initializes I/O part, namely the {@link #asDataInput} and {@link 
#asByteBuffer} fields.
      * Note that some or all of those fields may still be null after this 
method call.
      *
      * @see #rewind()
      */
     private void initialize() throws IOException {
-        if (input instanceof DataInput) {
-            asDataInput = (DataInput) input;
+        if (storage instanceof DataInput) {
+            asDataInput = (DataInput) storage;
         } else {
             /*
-             * Creates a ChannelImageInputStream instance. We really need that 
specific type - do NOT use
-             * the ImageIO.createImageInputStream(Object) method - because 
some SIS data stores will want
-             * to access directly the channel and the buffer.
+             * Creates a ChannelImageInputStream instance. We really need that 
specific type because some
+             * SIS data stores will want to access directly the channel and 
the buffer. We will fallback
+             * on the ImageIO.createImageInputStream(Object) method only in 
last resort.
              */
-            final ReadableByteChannel channel = IOUtilities.open(input, null);
+            final ReadableByteChannel channel = IOUtilities.open(storage, 
null);
             if (channel != null) {
                 final ByteBuffer buffer = ByteBuffer.allocate(4096);
-                asDataInput = new ChannelImageInputStream(getInputName(), 
channel, buffer, false);
+                asDataInput = new ChannelImageInputStream(getStorageName(), 
channel, buffer, false);
                 asByteBuffer = buffer.asReadOnlyBuffer();
+            } else {
+                asDataInput = ImageIO.createImageInputStream(storage);
             }
         }
         if (asDataInput instanceof ImageInputStream) {
@@ -227,27 +310,65 @@ public class DataStoreConnection impleme
      * <p>In the default implementation, this method does nothing if {@link 
#asDataInput()}
      * returns {@code null}.</p>
      *
-     * @throws IOException If the stream is open but can not be rewinded.
+     * @throws DataStoreException If the stream is open but can not be 
rewinded.
      */
-    public void rewind() throws IOException {
+    public void rewind() throws DataStoreException {
         /*
          * Restores the ImageInputStream to its original position if possible. 
Note that in
          * the ChannelImageInputStream, this may reload the buffer content if 
necessary.
          */
         if (asDataInput instanceof ImageInputStream) try {
             ((ImageInputStream) asDataInput).seek(streamOrigin);
-        } catch (IndexOutOfBoundsException e) {
-            throw new 
IOException(Errors.format(Errors.Keys.StreamIsForwardOnly_1, getInputName()), 
e);
+        } catch (IOException | IndexOutOfBoundsException e) {
+            throw new 
DataStoreException(Errors.format(Errors.Keys.StreamIsForwardOnly_1, 
getStorageName()), e);
         }
         /*
-         * If the buffer is non-null, then the way we implemented 
'initialize()' guarantees that
-         * 'asDataInput' is an instance of ChannelImageInputStream. So we copy 
the position and
-         * limits from the buffer. Note that this copy must be performed after 
the above 'seek',
-         * because the seek operation may have modified the buffer position 
and limit.
+         * Copy the position and limits from the buffer. Note that this copy 
must be performed after the
+         * above 'seek', because the seek operation may have modified the 
buffer position and limit.
          */
-        if (asByteBuffer != null) {
+        if (asByteBuffer != null && asDataInput instanceof 
ChannelImageInputStream) {
             final ByteBuffer buffer = ((ChannelImageInputStream) 
asDataInput).buffer;
             
asByteBuffer.clear().limit(buffer.limit()).position(buffer.position());
+            asByteBuffer.order(buffer.order());
+        }
+    }
+
+    /**
+     * Closes all streams and connections created by this object, and closes 
the storage it is closeable.
+     * This method closes the objects created by {@link #asDataInput()} and 
{@link #asDatabase()}, if any,
+     * then closes the {@linkplain #getStorage() storage} if it is closeable.
+     *
+     * <p>This method shall be invoked <strong>only</strong> if no {@link 
DataStore} accepted this input.
+     * Invoking this method in a {@code try} … {@code finally} block is 
usually not appropriate.</p>
+     *
+     * @throws DataStoreException If an error occurred while closing the 
stream or database connection.
+     */
+    public void close() throws DataStoreException {
+        try {
+            if (asDatabase != null) {
+                asDatabase.close();
+            }
+            if (asDataInput instanceof AutoCloseable) {
+                ((AutoCloseable) asDataInput).close();
+                /*
+                 * On JDK6, ImageInputStream does not extend Closeable and must
+                 * be checked explicitely. On JDK7, this is not needed anymore.
+                 */
+            } else if (storage instanceof AutoCloseable) {
+                /*
+                 * Close only if we didn't closed 'asDataInput', because 
closing 'asDataInput'
+                 * automatically close the 'storage' if the former is a 
wrapper for the later.
+                 * Since AutoCloseable.close() is not guaranteed to be 
indempotent, we should
+                 * avoid to call it (indirectly) twice.
+                 */
+                ((AutoCloseable) storage).close();
+            }
+        } catch (Exception e) {
+            throw new DataStoreException(e);
+        } finally {
+            asDatabase   = null;
+            asDataInput  = null;
+            asByteBuffer = null;
         }
     }
 
@@ -257,7 +378,7 @@ public class DataStoreConnection impleme
     @Override
     public String toString() {
         final StringBuilder buffer = new StringBuilder(40);
-        
buffer.append(getClass().getSimpleName()).append("[“").append(getInputName());
+        
buffer.append(Classes.getShortClassName(this)).append("[“").append(getStorageName());
         // TODO: more info here.
         return buffer.append("”]").toString();
     }


Reply via email to