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