View Javadoc

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 }