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    }