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
020package org.apache.james.mime4j.storage;
021
022import java.io.IOException;
023import 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 */
045public 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}