001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.jdbc;
017
018import java.io.ByteArrayInputStream;
019import java.io.IOException;
020import java.io.InputStream;
021import java.math.BigDecimal;
022import java.text.ParseException;
023import java.text.SimpleDateFormat;
024import java.util.Calendar;
025import java.util.Date;
026import javax.jcr.Binary;
027import javax.jcr.PropertyType;
028import javax.jcr.RepositoryException;
029import javax.jcr.Value;
030import javax.jcr.ValueFormatException;
031
032/**
033 * A factory class which creates {@link javax.jcr.Value} instances from arbitrary objects (avoiding the dependency on the real
034 * {@link javax.jcr.ValueFactory} implementations from other modules).
035 * 
036 * @author Horia Chiorean
037 */
038public final class JdbcJcrValueFactory {
039
040    protected static final SimpleDateFormat ISO8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
041
042    private JdbcJcrValueFactory() {
043    }
044
045    /**
046     * Creates a new {@link javax.jcr.Value} instance to be used by the JDBC driver.
047     * 
048     * @param value an actual value which will be wrapped by the JCR value; may be null
049     * @return either a {@link javax.jcr.Value} instance or {@code null} if the given argument is {@code null} or is the string
050     *         {@code NULL}
051     */
052    public static Value createValue( Object value ) {
053        if (value == null) {
054            return null;
055        }
056        if (value instanceof String && ((String)value).equalsIgnoreCase("NULL")) {
057            return null;
058        }
059        return new JdbcJcrValue(value);
060    }
061
062    private static class JdbcJcrValue implements Value {
063
064        private final Object value;
065
066        protected JdbcJcrValue( Object value ) {
067            assert value != null;
068            this.value = value;
069        }
070
071        @Override
072        public boolean getBoolean() throws IllegalStateException {
073            if (value instanceof Boolean) {
074                return (Boolean)value;
075            }
076            return Boolean.parseBoolean(value.toString());
077        }
078
079        @Override
080        public Calendar getDate() throws ValueFormatException, IllegalStateException, RepositoryException {
081            if (value instanceof Date) {
082                Calendar c = Calendar.getInstance();
083                c.setTime((Date)value);
084                return c;
085            } else if (value instanceof Calendar) {
086                return (Calendar)value;
087            }
088
089            try {
090                Date iso8601Format = ISO8601.parse(value.toString());
091                Calendar c = Calendar.getInstance();
092                c.setTime(iso8601Format);
093                return c;
094            } catch (ParseException e) {
095                throw new ValueFormatException("Value not instance of Date", e);
096            }
097        }
098
099        @Override
100        public double getDouble() throws ValueFormatException, IllegalStateException, RepositoryException {
101            if (value instanceof Number) {
102                return ((Number)value).doubleValue();
103            }
104
105            try {
106                return Double.parseDouble(value.toString());
107            } catch (NumberFormatException e) {
108                throw new ValueFormatException("Value not a Double", e);
109            }
110        }
111
112        @Override
113        public long getLong() throws ValueFormatException, IllegalStateException, RepositoryException {
114            if (value instanceof Number) {
115                return ((Number)value).longValue();
116            }
117
118            try {
119                return Long.parseLong(value.toString());
120            } catch (NumberFormatException e) {
121                throw new ValueFormatException("Value not a Long");
122            }
123        }
124
125        @Override
126        public Binary getBinary() throws RepositoryException {
127            if (value instanceof Binary) {
128                return ((Binary)value);
129            }
130            if (value instanceof byte[]) {
131                final byte[] bytes = (byte[])value;
132                return new Binary() {
133                    @Override
134                    public void dispose() {
135                    }
136
137                    @Override
138                    public long getSize() {
139                        return bytes.length;
140                    }
141
142                    @Override
143                    public InputStream getStream() {
144                        return new ByteArrayInputStream(bytes);
145                    }
146
147                    @Override
148                    public int read( byte[] b,
149                                     long position ) throws IOException {
150                        if (getSize() <= position) {
151                            return -1;
152                        }
153                        InputStream stream = null;
154                        try {
155                            stream = getStream();
156                            // Read/skip the next 'position' bytes ...
157                            long skip = position;
158                            while (skip > 0) {
159                                long skipped = stream.skip(skip);
160                                if (skipped <= 0) {
161                                    return -1;
162                                }
163                                skip -= skipped;
164                            }
165                            return stream.read(b);
166                        } finally {
167                            if (stream != null) {
168                                try {
169                                    stream.close();
170                                } catch (Exception e) {
171                                    // ignore
172                                }
173                            }
174                        }
175                    }
176
177                };
178            }
179            throw new ValueFormatException("Value not a Binary");
180        }
181
182        @Override
183        public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
184            if (value instanceof BigDecimal) {
185                return ((BigDecimal)value);
186            }
187            try {
188                return new BigDecimal(value.toString());
189            } catch (NumberFormatException e) {
190                throw new ValueFormatException("Value not a Decimal");
191            }
192        }
193
194        @Override
195        public InputStream getStream() throws IllegalStateException, RepositoryException {
196            if (value instanceof Binary) {
197                return ((Binary)value).getStream();
198            }
199            if (value instanceof InputStream) {
200                return ((InputStream)value);
201            }
202            if (value instanceof byte[]) {
203                return new ByteArrayInputStream((byte[])value);
204            }
205            throw new ValueFormatException("Value not an InputStream");
206        }
207
208        @Override
209        public String getString() throws IllegalStateException {
210            if (value instanceof String) {
211                return (String)value;
212            }
213            return value.toString();
214        }
215
216        @Override
217        public int getType() {
218            if (value instanceof String) {
219                return PropertyType.STRING;
220            }
221            if (value instanceof Boolean) {
222                return PropertyType.BOOLEAN;
223            }
224            if (value instanceof Date || value instanceof Calendar) {
225                return PropertyType.DATE;
226            }
227            if (value instanceof Double || value instanceof Float) {
228                return PropertyType.DOUBLE;
229            }
230            if (value instanceof Long || value instanceof Integer) {
231                return PropertyType.LONG;
232            }
233            if (value instanceof BigDecimal) {
234                return PropertyType.DECIMAL;
235            }
236            if (value instanceof byte[] || value instanceof Binary || value instanceof InputStream) {
237                return PropertyType.BINARY;
238            }
239            return PropertyType.UNDEFINED;
240        }
241    }
242
243}