Package org.apache.jackrabbit.spi.commons.value

Source Code of org.apache.jackrabbit.spi.commons.value.QValueFactoryImpl

/*
* 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.jackrabbit.spi.commons.value;

import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.QValue;
import org.apache.jackrabbit.spi.QValueFactory;
import org.apache.jackrabbit.util.ISO8601;
import org.apache.jackrabbit.util.TransientFileFactory;

import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFormatException;
import javax.jcr.Binary;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.Calendar;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;

/**
* <code>QValueFactoryImpl</code>...
*/
public final class QValueFactoryImpl extends AbstractQValueFactory {

    private static final QValueFactory INSTANCE = new QValueFactoryImpl();

    private QValueFactoryImpl() {
    }

    public static QValueFactory getInstance() {
        return INSTANCE;
    }

    //------------------------------------------------------< QValueFactory >---
    /**
     * @see QValueFactory#create(String, int)
     */
    public QValue create(String value, int type) throws RepositoryException {
        if (value == null) {
            throw new IllegalArgumentException("Cannot create QValue from null value.");
        }

        try {
            switch (type) {
                case PropertyType.BOOLEAN:
                    return (Boolean.valueOf(value).booleanValue()) ?
                            QValueImpl.TRUE :
                            QValueImpl.FALSE;
                case PropertyType.DATE: {
                        Calendar cal = ISO8601.parse(value);
                        if (cal == null) {
                            throw new ValueFormatException("not a valid date: " + value);
                        }
                        return new DateQValue(cal);
                    }
                case PropertyType.DOUBLE:
                    return new QValueImpl(Double.valueOf(value));
                case PropertyType.LONG:
                    return new QValueImpl(Long.valueOf(value));
                case PropertyType.DECIMAL:
                    return new QValueImpl(new BigDecimal(value));
                case PropertyType.URI:
                    return new QValueImpl(URI.create(value));
                case PropertyType.PATH:
                    return new QValueImpl(PATH_FACTORY.create(value));
                case PropertyType.NAME:
                    return new QValueImpl(NAME_FACTORY.create(value));
                case PropertyType.STRING:
                case PropertyType.REFERENCE:
                case PropertyType.WEAKREFERENCE:
                    return new QValueImpl(value, type);
                case PropertyType.BINARY:
                    return new BinaryQValue(value.getBytes(DEFAULT_ENCODING));
                // default: invalid type specified -> see below.
            }
        } catch (IllegalArgumentException ex) {
            // given String value cannot be converted to Long/Double/Path/Name
            throw new ValueFormatException(ex);
        } catch (UnsupportedEncodingException ex) {
            throw new RepositoryException(ex);
        }

        // invalid type specified:
        throw new IllegalArgumentException("illegal type " + type);
    }

    /**
     * @see QValueFactory#create(Calendar)
     */
    public QValue create(Calendar value) {
        if (value == null) {
            throw new IllegalArgumentException("Cannot create QValue from null value.");
        }
        // Calendar is not constant, must create a clone
        return new DateQValue((Calendar) value.clone());
    }

    /**
     * @see QValueFactory#create(double)
     */
    public QValue create(double value) {
        return new QValueImpl(Double.valueOf(value));
    }

    /**
     * @see QValueFactory#create(long)
     */
    public QValue create(long value) {
        return new QValueImpl(Long.valueOf(value));
    }

    /**
     * @see QValueFactory#create(boolean)
     */
    public QValue create(boolean value) {
        if (value) {
            return QValueImpl.TRUE;
        } else {
            return QValueImpl.FALSE;
        }
    }

    /**
     * @see QValueFactory#create(Name)
     */
    public QValue create(Name value) {
        if (value == null) {
            throw new IllegalArgumentException("Cannot create QValue from null value.");
        }
        return new QValueImpl(value);
    }

    /**
     * @see QValueFactory#create(Path)
     */
    public QValue create(Path value) {
        if (value == null) {
            throw new IllegalArgumentException("Cannot create QValue from null value.");
        }
        return new QValueImpl(value);
    }

    /**
     * @see QValueFactory#create(URI)
     */
    public QValue create(URI value) {
        if (value == null) {
            throw new IllegalArgumentException("Cannot create QValue from null value.");
        }
        return new QValueImpl(value);
    }

    /**
     * @see QValueFactory#create(URI)
     */
    public QValue create(BigDecimal value) {
        if (value == null) {
            throw new IllegalArgumentException("Cannot create QValue from null value.");
        }
        return new QValueImpl(value);
    }

    /**
     * @see QValueFactory#create(byte[])
     */
    public QValue create(byte[] value) {
        if (value == null) {
            throw new IllegalArgumentException("Cannot create QValue from null value.");
        }
        return new BinaryQValue(value);
    }

    /**
     * @see QValueFactory#create(InputStream)
     */
    public QValue create(InputStream value) throws IOException {
        if (value == null) {
            throw new IllegalArgumentException("Cannot create QValue from null value.");
        }
        return new BinaryQValue(value);
    }

    /**
     * @see QValueFactory#create(File)
     */
    public QValue create(File value) throws IOException {
        if (value == null) {
            throw new IllegalArgumentException("Cannot create QValue from null value.");
        }
        return new BinaryQValue(value);
    }


    //--------------------------------------------------------< Inner Class >---
    /**
     * <code>QValue</code> implementation for all valid <code>PropertyType</code>s
     * except for BINARY and DATE.
     * @see QValueFactoryImpl.BinaryQValue
     */
    private static class QValueImpl extends AbstractQValue implements Serializable {

        private static final QValue TRUE = new QValueImpl(Boolean.TRUE);
        private static final QValue FALSE = new QValueImpl(Boolean.FALSE);


        private QValueImpl(Object value, int type) {
            super(value, type);
        }

        private QValueImpl(String value, int type) {
            super(value, type);
        }

        private QValueImpl(Long value) {
            super(value);
        }

        private QValueImpl(Double value) {
            super(value);
        }

        private QValueImpl(BigDecimal value) {
            super(value);
        }

        private QValueImpl(Boolean value) {
            super(value);
        }

        private QValueImpl(Name value) {
            super(value);
        }

        private QValueImpl(Path value) {
            super(value);
        }

        private QValueImpl(URI value) {
            super(value);
        }

        //---------------------------------------------------------< QValue >---
        /**
         * @see QValue#getString()
         */
        public String getString() {
            return val.toString();
        }

        /**
         * @see QValue#getBinary()
         */
        public Binary getBinary() throws RepositoryException {
            // TODO FIXME consolidate Binary implementations
            return new Binary() {
                public InputStream getStream() throws RepositoryException {
                    return QValueImpl.this.getStream();
                }

                public int read(byte[] b, long position) throws IOException, RepositoryException {
                    InputStream in = getStream();
                    try {
                        in.skip(position);
                        return in.read(b);
                    } finally {
                        in.close();
                    }
                }

                public long getSize() throws RepositoryException {
                    return getLength();
                }

                public void dispose() {
                }

            };
        }

        /**
         * @see QValue#getStream()
         */
        public InputStream getStream() throws RepositoryException {
            try {
                // convert via string
                return new ByteArrayInputStream(getString().getBytes(DEFAULT_ENCODING));
            } catch (UnsupportedEncodingException e) {
                throw new RepositoryException(QValueFactoryImpl.DEFAULT_ENCODING + " is not supported encoding on this platform", e);
            }
    }
    }

    //--------------------------------------------------------< Inner Class >---
    /**
     * Extension for values of type {@link PropertyType#DATE}.
     */
    private static class DateQValue extends QValueImpl {

        private final String formattedStr;

        private DateQValue(Calendar value) {
            super(value, PropertyType.DATE);
            formattedStr = ISO8601.format(value);
        }

        /**
         * @return The formatted String of the internal Calendar value.
         * @see QValue#getString()
         * @see ISO8601#format(Calendar)
         */
        public String getString() {
            return formattedStr;
        }

        /**
         * @param obj
         * @return true if the given Object is a <code>DateQValue</code> with an
         * equal String representation.
         * @see Object#equals(Object)
         */
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj instanceof DateQValue) {
                DateQValue other = (DateQValue) obj;
                return formattedStr.equals(other.formattedStr);
            }
            return false;
        }

        /**
         * @return the hashCode of the formatted String of the Calender value.
         * @see Object#hashCode()
         */
        public int hashCode() {
            return formattedStr.hashCode();
        }
    }

    //--------------------------------------------------------< Inner Class >---
    /**
     * <code>BinaryQValue</code> represents a binary <code>Value</code> which is
     * backed by a resource or byte[]. Unlike <code>BinaryValue</code> it has no
     * state, i.e. the <code>getStream()</code> method always returns a fresh
     * <code>InputStream</code> instance.
     */
    private static class BinaryQValue implements QValue, Binary, Serializable {
        /**
         * empty array
         */
        private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];

        /**
         * max size for keeping tmp data in memory
         */
        private static final int MAX_BUFFER_SIZE = 0x10000;

        /**
         * underlying file
         */
        private transient File file;

        /**
         * flag indicating if this instance represents a <i>temporary</i> value
         * whose dynamically allocated resources can be explicitly freed on
         * {@link #discard()}.
         */
        private transient boolean temp;

        /**
         * Buffer for small-sized data
         */
        private byte[] buffer = BinaryQValue.EMPTY_BYTE_ARRAY;

        /**
         * Converted text
         */
        private transient String text = null;

        /**
         * Creates a new <code>BinaryQValue</code> instance from an
         * <code>InputStream</code>. The contents of the stream is spooled
         * to a temporary file or to a byte buffer if its size is smaller than
         * {@link #MAX_BUFFER_SIZE}.
         * <p/>
         * The new instance represents a <i>temporary</i> value whose dynamically
         * allocated resources will be freed explicitly on {@link #discard()}.
         *
         * @param in stream to be represented as a <code>BinaryQValue</code> instance
         * @throws IOException if an error occurs while reading from the stream or
         *                     writing to the temporary file
         */
        private BinaryQValue(InputStream in) throws IOException {
            this(in, true);
        }

        /**
         * Creates a new <code>BinaryQValue</code> instance from an
         * <code>InputStream</code>. The contents of the stream is spooled
         * to a temporary file or to a byte buffer if its size is smaller than
         * {@link #MAX_BUFFER_SIZE}.
         * <p/>
         * The <code>temp</code> parameter governs whether dynamically allocated
         * resources will be freed explicitly on {@link #discard()}. Note that any
         * dynamically allocated resources (temp file/buffer) will be freed
         * implicitly once this instance has been gc'ed.
         *
         * @param in stream to be represented as a <code>BinaryQValue</code> instance
         * @param temp flag indicating whether this instance represents a
         *             <i>temporary</i> value whose resources can be explicitly freed
         *             on {@link #discard()}.
         * @throws IOException if an error occurs while reading from the stream or
         *                     writing to the temporary file
         */
        private BinaryQValue(InputStream in, boolean temp) throws IOException {
            byte[] spoolBuffer = new byte[0x2000];
            int read;
            int len = 0;
            OutputStream out = null;
            File spoolFile = null;
            try {
                while ((read = in.read(spoolBuffer)) > 0) {
                    if (out != null) {
                        // spool to temp file
                        out.write(spoolBuffer, 0, read);
                        len += read;
                    } else if (len + read > BinaryQValue.MAX_BUFFER_SIZE) {
                        // threshold for keeping data in memory exceeded;
                        // create temp file and spool buffer contents
                        TransientFileFactory fileFactory = TransientFileFactory.getInstance();
                        spoolFile = fileFactory.createTransientFile("bin", null, null);
                        out = new FileOutputStream(spoolFile);
                        out.write(buffer, 0, len);
                        out.write(spoolBuffer, 0, read);
                        buffer = null;
                        len += read;
                    } else {
                        // reallocate new buffer and spool old buffer contents
                        byte[] newBuffer = new byte[len + read];
                        System.arraycopy(buffer, 0, newBuffer, 0, len);
                        System.arraycopy(spoolBuffer, 0, newBuffer, len, read);
                        buffer = newBuffer;
                        len += read;
                    }
                }
            } finally {
                in.close();
                if (out != null) {
                    out.close();
                }
            }

            // init vars
            file = spoolFile;
            this.temp = temp;
            // buffer is EMPTY_BYTE_ARRAY (default value)
        }

        /**
         * Creates a new <code>BinaryQValue</code> instance from a
         * <code>byte[]</code> array.
         *
         * @param bytes byte array to be represented as a <code>BinaryQValue</code>
         *              instance
         */
        private BinaryQValue(byte[] bytes) {
            buffer = bytes;
            file = null;
            // this instance is not backed by a temporarily allocated buffer
            temp = false;
        }

        /**
         * Creates a new <code>BinaryQValue</code> instance from a <code>File</code>.
         *
         * @param file file to be represented as a <code>BinaryQValue</code> instance
         * @throws IOException if the file can not be read
         */
        private BinaryQValue(File file) throws IOException {
            String path = file.getCanonicalPath();
            if (!file.isFile()) {
                throw new IOException(path + ": the specified file does not exist");
            }
            if (!file.canRead()) {
                throw new IOException(path + ": the specified file can not be read");
            }
            // this instance is backed by a 'real' file
            this.file = file;
            // this instance is not backed by temporarily allocated resource/buffer
            temp = false;
            // buffer is EMPTY_BYTE_ARRAY (default value)
        }

        //---------------------------------------------------------< QValue >---
        /**
         * @see QValue#getType()
         */
        public int getType() {
            return PropertyType.BINARY;
        }

        /**
         * Returns the length of this <code>BinaryQValue</code>.
         *
         * @return The length, in bytes, of this <code>BinaryQValue</code>,
         *         or -1L if the length can't be determined.
         * @see QValue#getLength()
         */
        public long getLength() {
            if (file != null) {
                // this instance is backed by a 'real' file
                if (file.exists()) {
                    return file.length();
                } else {
                    return -1;
                }
            } else {
                // this instance is backed by an in-memory buffer
                return buffer.length;
            }
        }

        /**
         * @see QValue#getString()
         */
        public String getString() throws RepositoryException {
            if (text == null) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                try {
                    spool(out);
                    byte[] data = out.toByteArray();
                    text = new String(data, QValueFactoryImpl.DEFAULT_ENCODING);
                } catch (UnsupportedEncodingException e) {
                    throw new RepositoryException(QValueFactoryImpl.DEFAULT_ENCODING
                        + " not supported on this platform", e);
                } catch (IOException e) {
                    throw new ValueFormatException("conversion from stream to string failed", e);
                } finally {
                    try {
                        out.close();
                    } catch (IOException e) {
                        // ignore
                    }
                }
            }
            return text;
        }

        /**
         * @see QValue#getStream()
         */
        public InputStream getStream() throws RepositoryException {
            // always return a 'fresh' stream
            if (file != null) {
                // this instance is backed by a 'real' file
                try {
                    return new FileInputStream(file);
                } catch (FileNotFoundException fnfe) {
                    throw new RepositoryException("file backing binary value not found",
                        fnfe);
                }
            } else {
                return new ByteArrayInputStream(buffer);
            }
        }

        /**
         * @see QValue#getName()
         */
        public Name getName() throws RepositoryException {
            throw new UnsupportedOperationException();
        }

        /**
         * @see QValue#getCalendar()
         */
        public Calendar getCalendar() throws RepositoryException {
             Calendar cal = ISO8601.parse(getString());
             if (cal == null) {
                 throw new ValueFormatException("not a date string: " + getString());
             } else {
                 return cal;
             }
        }

        /**
         * @see QValue#getDouble()
         */
        public double getDouble() throws RepositoryException {
            try {
                return Double.parseDouble(getString());
            } catch (NumberFormatException ex) {
                throw new ValueFormatException(ex);
            }
        }

        /**
         * @see QValue#getLong()
         */
        public long getLong() throws RepositoryException {
            try {
                return Long.parseLong(getString());
            } catch (NumberFormatException ex) {
                throw new ValueFormatException(ex);
            }
        }

        /**
         * @see QValue#getBoolean()
         */
        public boolean getBoolean() throws RepositoryException {
            return Boolean.valueOf(getString());
        }

        /**
         * @see QValue#getPath()
         */
        public Path getPath() throws RepositoryException {
            throw new UnsupportedOperationException();
        }

        /**
         * @see QValue#getDecimal()
         */
        public BigDecimal getDecimal() throws RepositoryException {
            try {
                return new BigDecimal(getString());
            } catch (NumberFormatException ex) {
                throw new ValueFormatException(ex);
            }
        }

        /**
         * @see QValue#getURI()
         */
        public URI getURI() throws RepositoryException {
            try {
                return new URI(getString());
            } catch (URISyntaxException ex) {
                throw new ValueFormatException(ex);
            }
        }

        /**
         * @see QValue#getBinary()
         */
        public Binary getBinary() throws RepositoryException {
            return this;
        }

        /**
         * Frees temporarily allocated resources such as temporary file, buffer, etc.
         * If this <code>BinaryQValue</code> is backed by a persistent resource
         * calling this method will have no effect.
         * @see QValue#discard()
         */
        public void discard() {
            if (!temp) {
                // do nothing if this instance is not backed by temporarily
                // allocated resource/buffer
                return;
            }
            if (file != null) {
                // this instance is backed by a temp file
                file.delete();
            } else if (buffer != null) {
                // this instance is backed by an in-memory buffer
                buffer = EMPTY_BYTE_ARRAY;
            }
        }

        public void dispose() {
            discard();
        }

        //-----------------------------------------------< java.lang.Object >---
        /**
         * Returns a string representation of this <code>BinaryQValue</code>
         * instance. The string representation of a resource backed value is
         * the path of the underlying resource. If this instance is backed by an
         * in-memory buffer the generic object string representation of the byte
         * array will be used instead.
         *
         * @return A string representation of this <code>BinaryQValue</code> instance.
         */
        public String toString() {
            if (file != null) {
                // this instance is backed by a 'real' file
                return file.toString();
            } else {
                // this instance is backed by an in-memory buffer
                return buffer.toString();
            }
        }

        /**
         * {@inheritDoc}
         */
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj instanceof BinaryQValue) {
                BinaryQValue other = (BinaryQValue) obj;
                return ((file == null ? other.file == null : file.equals(other.file))
                    && Arrays.equals(buffer, other.buffer));
            }
            return false;
        }

        /**
         * Returns zero to satisfy the Object equals/hashCode contract.
         * This class is mutable and not meant to be used as a hash key.
         *
         * @return always zero
         * @see Object#hashCode()
         */
        public int hashCode() {
            return 0;
        }

        //----------------------------------------------------------------------
        /**
         * Spools the contents of this <code>BinaryQValue</code> to the given
         * output stream.
         *
         * @param out output stream
         * @throws RepositoryException if the input stream for this
         *                             <code>BinaryQValue</code> could not be obtained
         * @throws IOException         if an error occurs while while spooling
         */
        private void spool(OutputStream out) throws RepositoryException, IOException {
            InputStream in;
            if (file != null) {
                // this instance is backed by a 'real' file
                try {
                    in = new FileInputStream(file);
                } catch (FileNotFoundException fnfe) {
                    throw new RepositoryException("file backing binary value not found",
                        fnfe);
                }
            } else {
                // this instance is backed by an in-memory buffer
                in = new ByteArrayInputStream(buffer);
            }
            try {
                byte[] buffer = new byte[0x2000];
                int read;
                while ((read = in.read(buffer)) > 0) {
                    out.write(buffer, 0, read);
                }
            } finally {
                try {
                    in.close();
                } catch (IOException ignore) {
                }
            }
        }

        //-----------------------------< javx.jcr.Binary >----------------------
        /**
         * {@inheritDoc}
         */
        public int read(byte[] b, long position) throws IOException, RepositoryException {
            if (file != null) {
                // this instance is backed by a temp file
                RandomAccessFile raf = new RandomAccessFile(file, "r");
                raf.seek(position);
                return raf.read(b);
            } else {
                // this instance is backed by an in-memory buffer
                int length = Math.min(b.length, buffer.length - (int) position);
                if (length > 0) {
                    System.arraycopy(buffer, (int) position, b, 0, length);
                    return length;
                } else {
                    return -1;
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public long getSize() throws RepositoryException {
            return getLength();
        }

        //-----------------------------< Serializable >-------------------------

        private void writeObject(ObjectOutputStream out)
                throws IOException {
            out.defaultWriteObject();
            // write hasFile marker
            out.writeBoolean(file != null);
            // then write file if necessary
            if (file != null) {
                byte[] buffer = new byte[4096];
                int bytes;
                InputStream stream = new FileInputStream(file);
                while ((bytes = stream.read(buffer)) >= 0) {
                    // Write a segment of the input stream
                    if (bytes > 0) {
                        // just to ensure that no 0 is written
                        out.writeInt(bytes);
                        out.write(buffer, 0, bytes);
                    }
                }
                // Write the end of stream marker
                out.writeInt(0);
                // close stream
                stream.close();
            }
        }

        private void readObject(ObjectInputStream in)
                throws IOException, ClassNotFoundException {
            in.defaultReadObject();
            boolean hasFile = in.readBoolean();
            if (hasFile) {
                file = File.createTempFile("binary-qvalue", "bin");

                OutputStream out = new FileOutputStream(file);
                byte[] buffer = new byte[4096];
                for (int bytes = in.readInt(); bytes > 0; bytes = in.readInt()) {
                    if (buffer.length < bytes) {
                        buffer = new byte[bytes];
                    }
                    in.readFully(buffer, 0, bytes);
                    out.write(buffer, 0, bytes);
                }
                out.close();
            }
            // deserialized value is always temp
            temp = true;
        }

    }

}
TOP

Related Classes of org.apache.jackrabbit.spi.commons.value.QValueFactoryImpl

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.