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.james.mime4j.storage;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.SequenceInputStream;
26
27 import org.apache.james.mime4j.util.ByteArrayBuffer;
28
29 /**
30 * A {@link StorageProvider} that keeps small amounts of data in memory and
31 * writes the remainder to another <code>StorageProvider</code> (the back-end)
32 * if a certain threshold size gets exceeded.
33 * <p>
34 * Example usage:
35 *
36 * <pre>
37 * StorageProvider tempStore = new TempFileStorageProvider();
38 * StorageProvider provider = new ThresholdStorageProvider(tempStore, 4096);
39 * DefaultStorageProvider.setInstance(provider);
40 * </pre>
41 */
42 public class ThresholdStorageProvider extends AbstractStorageProvider {
43
44 private final StorageProvider backend;
45 private final int thresholdSize;
46
47 /**
48 * Creates a new <code>ThresholdStorageProvider</code> for the given
49 * back-end using a threshold size of 2048 bytes.
50 */
51 public ThresholdStorageProvider(StorageProvider backend) {
52 this(backend, 2048);
53 }
54
55 /**
56 * Creates a new <code>ThresholdStorageProvider</code> for the given
57 * back-end and threshold size.
58 *
59 * @param backend
60 * used to store the remainder of the data if the threshold size
61 * gets exceeded.
62 * @param thresholdSize
63 * determines how much bytes are kept in memory before that
64 * back-end storage provider is used to store the remainder of
65 * the data.
66 */
67 public ThresholdStorageProvider(StorageProvider backend, int thresholdSize) {
68 if (backend == null)
69 throw new IllegalArgumentException();
70 if (thresholdSize < 1)
71 throw new IllegalArgumentException();
72
73 this.backend = backend;
74 this.thresholdSize = thresholdSize;
75 }
76
77 public StorageOutputStream createStorageOutputStream() {
78 return new ThresholdStorageOutputStream();
79 }
80
81 private final class ThresholdStorageOutputStream extends
82 StorageOutputStream {
83
84 private final ByteArrayBuffer head;
85 private StorageOutputStream tail;
86
87 public ThresholdStorageOutputStream() {
88 final int bufferSize = Math.min(thresholdSize, 1024);
89 head = new ByteArrayBuffer(bufferSize);
90 }
91
92 @Override
93 public void close() throws IOException {
94 super.close();
95
96 if (tail != null)
97 tail.close();
98 }
99
100 @Override
101 protected void write0(byte[] buffer, int offset, int length)
102 throws IOException {
103 int remainingHeadSize = thresholdSize - head.length();
104 if (remainingHeadSize > 0) {
105 int n = Math.min(remainingHeadSize, length);
106 head.append(buffer, offset, n);
107 offset += n;
108 length -= n;
109 }
110
111 if (length > 0) {
112 if (tail == null)
113 tail = backend.createStorageOutputStream();
114
115 tail.write(buffer, offset, length);
116 }
117 }
118
119 @Override
120 protected Storage toStorage0() throws IOException {
121 if (tail == null)
122 return new MemoryStorageProvider.MemoryStorage(head.buffer(),
123 head.length());
124
125 return new ThresholdStorage(head.buffer(), head.length(), tail
126 .toStorage());
127 }
128
129 }
130
131 private static final class ThresholdStorage implements Storage {
132
133 private byte[] head;
134 private final int headLen;
135 private Storage tail;
136
137 public ThresholdStorage(byte[] head, int headLen, Storage tail) {
138 this.head = head;
139 this.headLen = headLen;
140 this.tail = tail;
141 }
142
143 public void delete() {
144 if (head != null) {
145 head = null;
146 tail.delete();
147 tail = null;
148 }
149 }
150
151 public InputStream getInputStream() throws IOException {
152 if (head == null)
153 throw new IllegalStateException("storage has been deleted");
154
155 InputStream headStream = new ByteArrayInputStream(head, 0, headLen);
156 InputStream tailStream = tail.getInputStream();
157 return new SequenceInputStream(headStream, tailStream);
158 }
159
160 }
161 }