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