001 /****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one *
003 * or more contributor license agreements. See the NOTICE file *
004 * distributed with this work for additional information *
005 * regarding copyright ownership. The ASF licenses this file *
006 * to you under the Apache License, Version 2.0 (the *
007 * "License"); you may not use this file except in compliance *
008 * with the License. You may obtain a copy of the License at *
009 * *
010 * http://www.apache.org/licenses/LICENSE-2.0 *
011 * *
012 * Unless required by applicable law or agreed to in writing, *
013 * software distributed under the License is distributed on an *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015 * KIND, either express or implied. See the License for the *
016 * specific language governing permissions and limitations *
017 * under the License. *
018 ****************************************************************/
019
020 package org.apache.james.mime4j.storage;
021
022 import java.io.IOException;
023 import java.io.InputStream;
024
025 /**
026 * <p>
027 * A wrapper around another {@link Storage} that also maintains a reference
028 * counter. The inner storage gets deleted only if the reference counter reaches
029 * zero.
030 * </p>
031 * <p>
032 * Reference counting is used to delete the storage when it is no longer needed.
033 * So, any users of this class should note:
034 * </p>
035 * <ul>
036 * <li>The reference count is set up one on construction. In all other cases,
037 * {@link #addReference()} should be called when the storage is shared.</li>
038 * <li>The caller of {@link #addReference()} should ensure that
039 * {@link #delete()} is called once and only once.</li>
040 * <li>Sharing the {@link Storage} instance passed into
041 * {@link #MultiReferenceStorage(Storage)} may lead to miscounting and premature
042 * deletion</li>
043 * </ul>
044 */
045 public class MultiReferenceStorage implements Storage {
046
047 private final Storage storage;
048 private int referenceCounter;
049
050 /**
051 * Creates a new <code>MultiReferenceStorage</code> instance for the given
052 * back-end. The reference counter is initially set to one so the caller
053 * does not have to call {@link #addReference()} after this constructor.
054 *
055 * @param storage
056 * storage back-end that should be reference counted.
057 * @throws IllegalArgumentException
058 * when storage is null
059 */
060 public MultiReferenceStorage(Storage storage) {
061 if (storage == null)
062 throw new IllegalArgumentException();
063
064 this.storage = storage;
065 this.referenceCounter = 1; // caller holds first reference
066 }
067
068 /**
069 * Increments the reference counter.
070 *
071 * @throws IllegalStateException
072 * if the reference counter is zero which implies that the
073 * backing storage has already been deleted.
074 */
075 public void addReference() {
076 incrementCounter();
077 }
078
079 /**
080 * Decrements the reference counter and deletes the inner
081 * <code>Storage</code> object if the reference counter reaches zero.
082 * <p>
083 * A client that holds a reference to this object must make sure not to
084 * invoke this method a second time.
085 *
086 * @throws IllegalStateException
087 * if the reference counter is zero which implies that the
088 * backing storage has already been deleted.
089 */
090 public void delete() {
091 if (decrementCounter()) {
092 storage.delete();
093 }
094 }
095
096 /**
097 * Returns the input stream of the inner <code>Storage</code> object.
098 *
099 * @return an input stream.
100 */
101 public InputStream getInputStream() throws IOException {
102 return storage.getInputStream();
103 }
104
105 /**
106 * Synchronized increment of reference count.
107 *
108 * @throws IllegalStateException
109 * when counter is already zero
110 */
111 private synchronized void incrementCounter() {
112 if (referenceCounter == 0)
113 throw new IllegalStateException("storage has been deleted");
114
115 referenceCounter++;
116 }
117
118 /**
119 * Synchronized decrement of reference count.
120 *
121 * @return true when counter has reached zero, false otherwise
122 * @throws IllegalStateException
123 * when counter is already zero
124 */
125 private synchronized boolean decrementCounter() {
126 if (referenceCounter == 0)
127 throw new IllegalStateException("storage has been deleted");
128
129 return --referenceCounter == 0;
130 }
131 }