1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.chemistry.opencmis.jcr;
21
22 import javax.jcr.Binary;
23 import javax.jcr.RepositoryException;
24 import java.io.ByteArrayInputStream;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileNotFoundException;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.io.RandomAccessFile;
33
34 /**
35 * <code>JcrBinary</code> implements the JCR <code>Binary</code> interface.
36 * This is mostly a copy from org.apache.jackrabbit.value.BinaryImpl in
37 * Apache Jackrabbit's jcr-commons module.
38 */
39 public class JcrBinary implements Binary {
40
41 /**
42 * empty array
43 */
44 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
45
46 /**
47 * max size for keeping tmp data in memory
48 */
49 private static final int MAX_BUFFER_SIZE = 0x10000;
50
51 /**
52 * underlying tmp file
53 */
54 private final File tmpFile;
55
56 /**
57 * buffer for small-sized data
58 */
59 private byte[] buffer = EMPTY_BYTE_ARRAY;
60
61 public static final Binary EMPTY = new JcrBinary(EMPTY_BYTE_ARRAY);
62
63 /**
64 * Creates a new <code>JcrBinary</code> instance from an <code>InputStream</code>.
65 * The contents of the stream is spooled to a temporary file or to a byte buffer
66 * if its size is smaller than {@link #MAX_BUFFER_SIZE}.
67 * The input stream is closed by this implementation.
68 *
69 * @param in stream to be represented as a <code>JcrBinary</code> instance
70 * @throws IOException if an error occurs while reading from the stream or
71 * writing to the temporary file
72 */
73 public JcrBinary(InputStream in) throws IOException {
74 byte[] spoolBuffer = new byte[0x2000];
75 int read;
76 int len = 0;
77 OutputStream out = null;
78 File spoolFile = null;
79 try {
80 while ((read = in.read(spoolBuffer)) > 0) {
81 if (out != null) {
82 // spool to temp file
83 out.write(spoolBuffer, 0, read);
84 len += read;
85 }
86 else if (len + read > MAX_BUFFER_SIZE) {
87 // threshold for keeping data in memory exceeded;
88 // create temp file and spool buffer contents
89
90 spoolFile = File.createTempFile("bin", null, null);
91 out = new FileOutputStream(spoolFile);
92 out.write(buffer, 0, len);
93 out.write(spoolBuffer, 0, read);
94 buffer = null;
95 len += read;
96 }
97 else {
98 // reallocate new buffer and spool old buffer contents
99 byte[] newBuffer = new byte[len + read];
100 System.arraycopy(buffer, 0, newBuffer, 0, len);
101 System.arraycopy(spoolBuffer, 0, newBuffer, len, read);
102 buffer = newBuffer;
103 len += read;
104 }
105 }
106 }
107 finally {
108 in.close();
109 if (out != null) {
110 out.close();
111 }
112 }
113
114 // init fields
115 tmpFile = spoolFile;
116 }
117
118 /**
119 * Creates a new <code>JcrBinary</code> instance from a <code>byte[]</code> array.
120 *
121 * @param buffer byte array to be represented as a <code>JcrBinary</code> instance
122 */
123 public JcrBinary(byte[] buffer) {
124 if (buffer == null) {
125 throw new IllegalArgumentException("buffer must be non-null");
126 }
127 this.buffer = buffer;
128 tmpFile = null;
129 }
130
131 public InputStream getStream() throws RepositoryException {
132 if (tmpFile != null) {
133 try {
134 // this instance is backed by a temp file
135 return new FileInputStream(tmpFile);
136 }
137 catch (FileNotFoundException e) {
138 throw new RepositoryException("already disposed");
139 }
140 }
141 else {
142 // this instance is backed by an in-memory buffer
143 return new ByteArrayInputStream(buffer);
144 }
145 }
146
147 public int read(byte[] b, long position) throws IOException, RepositoryException {
148 if (tmpFile != null) {
149 // this instance is backed by a temp file
150 RandomAccessFile raf = new RandomAccessFile(tmpFile, "r");
151 try {
152 raf.seek(position);
153 return raf.read(b);
154 }
155 finally {
156 raf.close();
157 }
158 }
159 else {
160 // this instance is backed by an in-memory buffer
161 int length = Math.min(b.length, buffer.length - (int) position);
162 if (length > 0) {
163 System.arraycopy(buffer, (int) position, b, 0, length);
164 return length;
165 }
166 else {
167 return -1;
168 }
169 }
170 }
171
172 public long getSize() throws RepositoryException {
173 if (tmpFile != null) {
174 // this instance is backed by a temp file
175 return tmpFile.exists() ? tmpFile.length() : -1;
176 }
177 else {
178 // this instance is backed by an in-memory buffer
179 return buffer.length;
180 }
181 }
182
183 public void dispose() {
184 if (tmpFile != null) {
185 // this instance is backed by a temp file
186 tmpFile.delete();
187 }
188 else {
189 // this instance is backed by an in-memory buffer
190 buffer = EMPTY_BYTE_ARRAY;
191 }
192 }
193
194 }