001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2010-2012 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.util;
011
012import java.util.*;
013
014/**
015 * Provides a way to pass objects via a string moniker.
016 *
017 * <p>This is useful if you are passing an object over an API that admits only
018 * strings (such as
019 * {@link java.sql.DriverManager#getConnection(String, java.util.Properties)})
020 * and where tricks such as {@link ThreadLocal} do not work. The callee needs
021 * to be on the same JVM, but other than that, the object does not need to have
022 * any special properties. In particular, it does not need to be serializable.
023 *
024 * <p>First, register the object to obtain a lock box entry. Every lock box
025 * entry has a string moniker that is very difficult to guess, is unique, and
026 * is not recycled. Pass that moniker to the callee, and from that moniker the
027 * callee can retrieve the entry and with it the object.
028 *
029 * <p>The entry cannot be forged and cannot be copied. If you lose the entry,
030 * you can no longer retrieve the object, and the entry will eventually be
031 * garbage-collected. If you call {@link #deregister(Entry)}, callees will no
032 * longer be able to look up an entry from its moniker.
033 *
034 * <p>The same is not true of the moniker string. Having the moniker string
035 * does not guarantee that the entry will not be removed. Therefore, the
036 * creator who called {@link #register(Object)} and holds the entry controls
037 * the lifecycle.
038 *
039 * <p>The moniker consists of the characters A..Z, a..z, 0..9, $, #, and is
040 * thus a valid (case-sensitive) identifier.
041 *
042 * <p>All methods are thread-safe.
043 *
044 * @author jhyde
045  * @since 2010/11/18
046 */
047public class LockBox {
048    private static final Object DUMMY = new Object();
049
050    /**
051     * Mapping from monikers to entries.
052     *
053     * <p>Per WeakHashMap: "An entry in a WeakHashMap will automatically be
054     * removed when its key is no longer in ordinary use. More precisely,
055     * the presence of a mapping for a given key will not prevent the key
056     * from being discarded by the garbage collector, that is, made
057     * finalizable, finalized, and then reclaimed. When a key has been
058     * discarded its entry is effectively removed from the map..."
059     *
060     * <p>LockBoxEntryImpl meets those constraints precisely. An entry will
061     * disappear when the caller forgets the key, or calls deregister. If
062     * the caller (or someone) still has the moniker, it is not sufficient
063     * to prevent the entry from being garbage collected.
064     */
065    private final Map<LockBoxEntryImpl, Object> map =
066        new WeakHashMap<LockBoxEntryImpl, Object>();
067    private final Random random = new Random();
068    private final byte[] bytes = new byte[16]; // 128 bit... secure enough
069    private long ordinal;
070
071    /**
072     * Creates a LockBox.
073     */
074    public LockBox() {
075    }
076
077    private static Object wrap(Object o) {
078        return o == null ? DUMMY : o;
079    }
080
081    private static Object unwrap(Object value) {
082        return value == DUMMY ? null : value;
083    }
084
085    /**
086     * Adds an object to the lock box, and returns a key for it.
087     *
088     * <p>The object may be null. The same object may be registered multiple
089     * times; each time it is registered, a new entry with a new string
090     * moniker is generated.
091     *
092     * @param o Object to register. May be null.
093     * @return Entry containing the object's string key and the object itself
094     */
095    public synchronized Entry register(Object o) {
096        String moniker = generateMoniker();
097        final LockBoxEntryImpl entry = new LockBoxEntryImpl(this, moniker);
098        map.put(entry, wrap(o));
099        return entry;
100    }
101
102    /**
103     * Generates a non-repeating, random string.
104     *
105     * <p>Must be called from synchronized context.
106     *
107     * @return Non-repeating random string
108     */
109    private String generateMoniker() {
110        // The prefixed ordinal ensures that the string never repeats. Of
111        // course, there will be a pattern to the first few chars of the
112        // returned string, but that doesn't matter for these purposes.
113        random.nextBytes(bytes);
114
115        // Remove trailing '='. It is padding required by base64 spec but does
116        // not help us.
117        String base64 = Base64.encodeBytes(bytes);
118        while (base64.endsWith("=")) {
119            base64 = base64.substring(0, base64.length() - 1);
120        }
121        // Convert '/' to '$' and '+' to '_'. The resulting moniker starts with
122        // a '$', and contains only A-Z, a-z, 0-9, _ and $; it is a valid
123        // identifier, and does not need to be quoted in XML or an HTTP URL.
124        base64 = base64.replace('/', '$');
125        base64 = base64.replace('+', '_');
126        return "$"
127               + Long.toHexString(++ordinal)
128               + base64;
129    }
130
131    /**
132     * Removes an entry from the lock box.
133     *
134     * <p>It is safe to call this method multiple times.
135     *
136     * @param entry Entry to deregister
137     * @return Whether the object was removed
138     */
139    public synchronized boolean deregister(Entry entry) {
140        return map.remove(entry) != null;
141    }
142
143    /**
144     * Retrieves an entry using its string moniker. Returns null if there is
145     * no entry with that moniker.
146     *
147     * <p>Successive calls for the same moniker do not necessarily return
148     * the same {@code Entry} object, but those entries'
149     * {@link LockBox.Entry#getValue()} will nevertheless return the same
150     * value.</p>
151     *
152     * @param moniker Moniker of the lock box entry
153     * @return Entry, or null if there is no entry with this moniker
154     */
155    public synchronized Entry get(String moniker) {
156        // Linear scan through keys. Not perfect, but safer than maintaining
157        // a map that might mistakenly allow/prevent GC.
158        for (LockBoxEntryImpl entry : map.keySet()) {
159            if (entry.moniker.equals(moniker)) {
160                return entry;
161            }
162        }
163        return null;
164    }
165
166    /**
167     * Entry in a {@link LockBox}.
168     *
169     * <p>Entries are created using {@link LockBox#register(Object)}.
170     *
171     * <p>The object can be retrieved using {@link #getValue()} if you have
172     * the entry, or {@link LockBox#get(String)} if you only have the
173     * string key.
174     *
175     * <p>Holding onto an Entry will prevent the entry, and the associated
176     * value from being garbage collected. Holding onto the moniker will
177     * not prevent garbage collection.</p>
178     */
179    public interface Entry
180    {
181        /**
182         * Returns the value in this lock box entry.
183         *
184         * @return Value in this lock box entry.
185         */
186        Object getValue();
187
188        /**
189         * String key by which to identify this object. Not null, not easily
190         * forged, and unique within the lock box.
191         *
192         * <p>Given this moniker, you retrieve the Entry using
193         * {@link LockBox#get(String)}. The retrieved Entry will will have the
194         * same moniker, and will be able to access the same value.</p>
195         *
196         * @return String key
197         */
198        String getMoniker();
199
200        /**
201         * Returns whether the entry is still valid. Returns false if
202         * {@link LockBox#deregister(mondrian.util.LockBox.Entry)} has been
203         * called on this Entry or any entry with the same moniker.
204         *
205         * @return whether entry is registered
206         */
207        boolean isRegistered();
208    }
209
210    /**
211     * Implementation of {@link Entry}.
212     *
213     * <p>It is important that entries cannot be forged. Therefore this class,
214     * and its constructor, are private. And equals and hashCode use object
215     * identity.
216     */
217    private static class LockBoxEntryImpl implements Entry {
218        private final LockBox lockBox;
219        private final String moniker;
220
221        private LockBoxEntryImpl(LockBox lockBox, String moniker) {
222            this.lockBox = lockBox;
223            this.moniker = moniker;
224        }
225
226        public Object getValue() {
227            final Object value = lockBox.map.get(this);
228            if (value == null) {
229                throw new RuntimeException(
230                    "LockBox has no entry with moniker [" + moniker + "]");
231            }
232            return unwrap(value);
233        }
234
235        public String getMoniker() {
236            return moniker;
237        }
238
239        public boolean isRegistered() {
240            return lockBox.map.containsKey(this);
241        }
242    }
243}
244
245// End LockBox.java