1 /*------------------------------------------------------------------------------
2 Name: BlasterCache.java
3 Project: xmlBlaster.org
4 Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
5 ------------------------------------------------------------------------------*/
6 package org.xmlBlaster.client;
7
8 import org.xmlBlaster.util.Global;
9 import org.xmlBlaster.util.XmlBlasterException;
10 import org.xmlBlaster.util.MsgUnit;
11 import org.xmlBlaster.util.def.Constants;
12 import org.xmlBlaster.client.key.UpdateKey;
13 import org.xmlBlaster.client.qos.UpdateQos;
14 import org.xmlBlaster.client.key.GetKey;
15 import org.xmlBlaster.client.qos.GetQos;
16
17 import java.util.*;
18
19 /**
20 * Caches the messages updated from xmlBlaster.
21 * <p />
22 * It is used to allow local (client side) cached messages
23 * which you can access with the <strong>synchronous</strong>
24 * getCached() method.
25 * <p />
26 * If XmlBlasterAccess has switched this cache on,
27 * a getCached() automatically makes a subscribe() behind the scenes as well
28 * and subsequent getCached() are high performing local calls.
29 * @author konrad.krafft@doubleslash.de
30 * @author xmlblaster@marcelruff.info
31 * @see org.xmlBlaster.client.XmlBlasterAccess#getCached(GetKey, GetQos)
32 * @see org.xmlBlaster.test.client.TestSynchronousCache
33 * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/client.cache.html">client.cache requirement</a>
34 */
35 public final class SynchronousCache
36 {
37 private static final String ME = "SynchronousCache";
38 private final Global glob;
39
40 /** key==getQueryString(GetKey getKey), value=subscriptionId */
41 private Hashtable query2SubId = null;
42 /**
43 * key==subscriptionId, value=dataHashtable<br />
44 * And dataHashtable key=keyOid, value=MsgUnit
45 */
46 private Hashtable subscriptions = null;
47 private int maxQueriesCached = 0;
48
49
50 /**
51 * Create a cache instance.
52 */
53 public SynchronousCache(Global glob, int maxQueriesCached) {
54 this.glob = glob;
55 this.query2SubId = new Hashtable();
56 this.subscriptions = new Hashtable();
57 this.maxQueriesCached = maxQueriesCached;
58 }
59
60 /**
61 * Remove a cache entry with the given subscriptionId.
62 * <p>
63 * This is usually called by the update() ERASE event
64 * </p>
65 */
66 public void removeEntry(String subId) {
67 Hashtable dataHashtable = (Hashtable)this.subscriptions.remove(subId);
68 if (dataHashtable == null) {
69 System.err.println("Expected to remove subId=" + subId + " " + toXml(""));
70 return;
71 }
72 synchronized (dataHashtable) {
73 dataHashtable.clear();
74 }
75
76 String query = getQueryString(subId);
77 if (query != null) {
78 this.query2SubId.remove(query);
79 }
80 else {
81 System.err.println("Expected to remove subId=" + subId + " from query2SubId: " + toXml(""));
82 }
83 }
84
85 /**
86 * Remove a MsgUnit from cache with the given query key string and keyOid.
87 * <p>
88 * This is usually called by the update() ERASE event for XPATH queries,
89 * when the last oid disappears the cache entry is removed
90 * </p>
91 */
92 public void removeEntryByQueryString(String query, String keyOid) {
93 String subId = (String)this.query2SubId.get(query);
94 if (subId != null) {
95 Hashtable dataHashtable = (Hashtable)this.subscriptions.get(subId);
96 if (dataHashtable != null) {
97 synchronized (dataHashtable) {
98 dataHashtable.remove(keyOid);
99 if (dataHashtable.size() < 1) {
100 this.subscriptions.remove(subId);
101 this.query2SubId.remove(query);
102 }
103 }
104 }
105 }
106 }
107
108 /**
109 * Remove a cache entry with the given query key string.
110 * <p>
111 * This is usually called by the update() ERASE event for EXACT queries.
112 * </p>
113 */
114 public void removeEntryByQueryString(String query) {
115 String subId = (String)this.query2SubId.remove(query);
116 if (subId != null) {
117 Hashtable dataHashtable = (Hashtable)this.subscriptions.remove(subId);
118 if (dataHashtable != null) {
119 synchronized (dataHashtable) {
120 dataHashtable.clear();
121 }
122 }
123 }
124 }
125
126 /**
127 * Access the query key for a given subscriptionId.
128 *
129 * Slow linear lookup ...
130 * @return null if not found
131 */
132 private String getQueryString(String subscriptionId) {
133 Enumeration queryEnum = this.query2SubId.keys();
134 while (queryEnum.hasMoreElements()) {
135 String query = (String)queryEnum.nextElement();
136 String tmpSubscriptionId = (String)this.query2SubId.get(query);
137 if (tmpSubscriptionId.equals(subscriptionId)) {
138 return query;
139 }
140 }
141 return null;
142 }
143
144 /**
145 * Updated the cache (add a new entry or replaces an existing or removes one).
146 * @return true if message was for cache, false if we are not interested in such a message.
147 */
148 public boolean update(String subId, UpdateKey updateKey, byte[] content, UpdateQos updateQos) throws XmlBlasterException {
149 Object obj = this.subscriptions.get(subId);
150 if(obj == null) {
151 return false;
152 }
153 if (updateQos.isErased()) {
154 String query = getQueryString(subId);
155 if (query != null) {
156 if (query.startsWith(Constants.EXACT)) {
157 removeEntryByQueryString(query);
158 return true;
159 }
160 else {
161 removeEntryByQueryString(query, updateKey.getOid());
162 return true;
163 }
164 }
165 else
166 return true;
167 }
168 Hashtable dataHashtable = (Hashtable)obj;
169 synchronized (dataHashtable) {
170 dataHashtable.put(updateKey.getOid(), new MsgUnit(updateKey.getData(), content, updateQos.getData()));
171 }
172 return true;
173 }
174
175 /**
176 * Access messages from cache
177 * @return null if no messages are found in cache
178 */
179 public MsgUnit[] get(GetKey getKey, GetQos getQos) throws XmlBlasterException {
180 MsgUnit[] messageUnits = null;
181
182 //Look into cache if xmlKey is already there
183 String subId = (String)this.query2SubId.get(getQueryString(getKey));
184
185 //if yes, return the content of the cache entry
186 if(subId != null) {
187 Hashtable dataHashtable = (Hashtable)this.subscriptions.get(subId);
188 if (dataHashtable != null) {
189 synchronized (dataHashtable) {
190 messageUnits = new MsgUnit[dataHashtable.size()];
191 int i = 0;
192 Enumeration values = dataHashtable.elements();
193 while (values.hasMoreElements()) {
194 messageUnits[i] = (MsgUnit)values.nextElement();
195 i++;
196 }
197 }
198 }
199 }
200
201 return messageUnits;
202 }
203
204 /**
205 * Create a unique key for our Hashtable from a GetKey
206 */
207 public String getQueryString(GetKey getKey) {
208 if (getKey.getData().isExact()) {
209 return Constants.EXACT+getKey.getOid();
210 }
211 else {
212 return getKey.getData().getQueryType()+getKey.getData().getQueryString().trim();
213 }
214 }
215
216 /**
217 * Creates an new entry in the cache
218 * <p />
219 * @return true - entry has been created
220 * false- cache is full
221 */
222 public boolean newEntry(String subId, GetKey getKey, MsgUnit[] units) throws XmlBlasterException {
223 String query = getQueryString(getKey);
224 if (this.query2SubId.get(query) != null)
225 return true; // Existed already
226
227 if(this.query2SubId.size() < this.maxQueriesCached) {
228 this.query2SubId.put(query, subId);
229 Hashtable dataHashtable = new Hashtable();
230 for( int i = 0; i < units.length; i++ )
231 dataHashtable.put(units[i].getKeyOid(), units[i]);
232 this.subscriptions.put(subId, dataHashtable);
233 return true;
234 }
235 else
236 return false;
237 }
238
239 /**
240 * Destroy all entries in the cash
241 */
242 public synchronized void clear() {
243 this.query2SubId.clear();
244 this.subscriptions.clear();
245 }
246
247 /**
248 * Return how full is this cache.
249 */
250 public int getNumQueriesCached() {
251 return this.subscriptions.size();
252 }
253
254 /**
255 * Return the registered subscriptions, for internal use only (to check cache).
256 * @return key==getQueryString(GetKey getKey), value=subscriptionId
257 */
258 public Hashtable getSubscriptions() {
259 return this.subscriptions;
260 }
261
262 /**
263 * Dump state of this object into a XML ASCII string.
264 * <br>
265 * @param extraOffset indenting of tags for nice output
266 * @return internal state of SynchronousCache as a XML ASCII string
267 */
268 public final String toXml(String extraOffset) {
269 StringBuffer sb = new StringBuffer(1024);
270 if (extraOffset == null) extraOffset = "";
271 String offset = Constants.OFFSET + extraOffset;
272
273 sb.append(offset).append("<SynchronousCache maxQueriesCached='").append(this.maxQueriesCached).append("'>");
274 Enumeration subIdEnum = this.subscriptions.keys();
275 while (subIdEnum.hasMoreElements()) {
276 String subscriptionId = (String)subIdEnum.nextElement();
277 Hashtable hash = (Hashtable)this.subscriptions.get(subscriptionId);
278 sb.append(offset).append(" <subscribe id='").append(subscriptionId).append("'/>");
279 }
280 Enumeration queryEnum = this.query2SubId.keys();
281 while (queryEnum.hasMoreElements()) {
282 String query = (String)queryEnum.nextElement();
283 String subscriptionId = (String)this.query2SubId.get(query);
284 sb.append(offset).append(" <query id='").append(query);
285 sb.append("' subscriptionId='").append(subscriptionId).append("'/>");
286 }
287 sb.append(offset).append("</SynchronousCache>");
288
289 return sb.toString();
290 }
291 } // SynchronousCache
syntax highlighted by Code2HTML, v. 0.9.1