KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > slide > cluster > ClusterCacheRefresher


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */

17 package org.apache.slide.cluster;
18
19 import java.util.Enumeration JavaDoc;
20 import java.util.EventListener JavaDoc;
21 import java.util.Iterator JavaDoc;
22 import java.util.Map JavaDoc;
23
24 import org.apache.commons.httpclient.Credentials;
25 import org.apache.commons.httpclient.UsernamePasswordCredentials;
26 import org.apache.commons.httpclient.protocol.Protocol;
27 import org.apache.slide.authenticate.CredentialsToken;
28 import org.apache.slide.authenticate.SecurityToken;
29 import org.apache.slide.common.Domain;
30 import org.apache.slide.common.NamespaceAccessToken;
31 import org.apache.slide.common.SlideTokenImpl;
32 import org.apache.slide.common.Uri;
33 import org.apache.slide.store.ExtendedStore;
34 import org.apache.slide.store.Store;
35 import org.apache.slide.util.conf.Configurable;
36 import org.apache.slide.util.conf.Configuration;
37 import org.apache.slide.util.conf.ConfigurationException;
38 import org.apache.slide.util.logger.Logger;
39 import org.apache.webdav.lib.NotificationListener;
40 import org.apache.webdav.lib.Subscriber;
41 import org.apache.webdav.lib.methods.DepthSupport;
42
43 /**
44  * <h3>Description</h3>
45  * <p>
46  * When configured properly this class will register with one or more external
47  * Slide instances and listen for changes. Upon notification of a change this
48  * class will cause the cache of the local Slide instance to be refreshed for
49  * the changed object.
50  * </p>
51  * <h3>Usage</h3>
52  * <p>
53  * Add the following to your Domain.xml inside the &lt;events&gt; node.
54  * </p>
55  *
56  * <pre>
57  *
58  *
59  * &lt;listener classname=&quot;org.apache.slide.cluster.ClusterCacheRefresher&quot;&gt;
60  * &lt;configuration&gt;
61  * &lt;node local-host=&quot;local.host.domain&quot;
62  * local-port=&quot;4444&quot;
63  * repository-host=&quot;remote.host.domain&quot;
64  * repository-port=&quot;8080&quot;
65  * repository-protocol=&quot;http&quot;
66  * username=&quot;root&quot;
67  * password=&quot;root&quot;
68  * /&gt;
69  * &lt;/configuration&gt;
70  * &lt;/listener&gt;
71  *
72  *
73  * </pre>
74  *
75  * <p>
76  * There should be one &lt;node&gt; element for each node in the cluster,
77  * <b>except </b> for the current node. ClusterCacheRefresher should not be
78  * configured to listen to itself except for testing purposes.
79  * </p>
80  * <h3>&lt;node&gt; attributes</h3>
81  * <table>
82  * <tr>
83  * <th>Attribute Name</th>
84  * <th>Required?</th>
85  * <th>Default Value</th>
86  * <th>Description</th>
87  * </tr>
88  * <tr>
89  * <td>local-host</td>
90  * <td>yes</td>
91  * <td>none</td>
92  * <td>A network-accessible name or ip-address where the remote Slide instance
93  * can reach <b>this server. </b></td>
94  * </tr>
95  * <tr>
96  * <td>local-port</td>
97  * <td>yes</td>
98  * <td>none</td>
99  * <td>A port number ClusterCacheRefresher can use to listen for notifications.
100  * <b>Must be unique. </b></td>
101  * </tr>
102  * <tr>
103  * <td>repository-host</td>
104  * <td>yes</td>
105  * <td>none</td>
106  * <td>A network-accessible name or ip-address of the remote Slide instance to
107  * monitor.</td>
108  * </tr>
109  * <tr>
110  * <td>repository-port</td>
111  * <td>yes</td>
112  * <td>none</td>
113  * <td>The port the remote Slide instance is running on.</td>
114  * </tr>
115  * <tr>
116  * <td>repository-protocol</td>
117  * <td>no</td>
118  * <td>http</td>
119  * <td>The protocol the remote Slide instance is using. Must be one of "http"
120  * or "https".</td>
121  * </tr>
122  * <tr>
123  * <td>username</td>
124  * <td>no</td>
125  * <td>none</td>
126  * <td>The username to use to connect to the remote Slide instance.</td>
127  * </tr>
128  * <tr>
129  * <td>password</td>
130  * <td>no</td>
131  * <td>none</td>
132  * <td>The password that goes with the username.</td>
133  * </tr>
134  * <tr>
135  * <td>repository-domain</td>
136  * <td>no</td>
137  * <td>/slide</td>
138  * <td>The context path of the remote Slide instance.</td>
139  * </tr>
140  * <tr>
141  * <td>poll-interval</td>
142  * <td>no</td>
143  * <td>60000</td>
144  * <td>The number of milliseconds to wait between polling the remote Slide
145  * instance for any changes. Polling for changes is a backup only, so this value
146  * can be set fairly high.</td>
147  * </tr>
148  * <tr>
149  * <td>udp</td>
150  * <td>no</td>
151  * <td>true</td>
152  * <td>Must be "true" or "false". Indicates whether to use udp or tcp to listen
153  * for notifications.</td>
154  * </tr>
155  * <tr>
156  * <td>base-uri</td>
157  * <td>no</td>
158  * <td>/</td>
159  * <td>The base path to monitor for changes. Will be appended to the
160  * repository-domain.</td>
161  * </tr>
162  * <tr>
163  * <td>subscription-lifetime</td>
164  * <td>no</td>
165  * <td>3600</td>
166  * <td>The number of seconds a subscription should last. Subscriptions are
167  * automatically refreshed. Do not set this value too high.</td>
168  * </tr>
169  * <tr>
170  * <td>notification-delay</td>
171  * <td>no</td>
172  * <td>0</td>
173  * <td>Number of seconds the remote Slide instance should wait before sending a
174  * notification of a change.</td>
175  * </tr>
176  * </table>
177  */

178 public class ClusterCacheRefresher implements EventListener JavaDoc, Configurable {
179     protected static final String JavaDoc LOG_CHANNEL = ClusterCacheRefresher.class.getName();
180
181     protected NotificationListener listener;
182
183     public ClusterCacheRefresher() {
184         Domain.log("Creating ClusterCacheRefresher", LOG_CHANNEL, Logger.INFO);
185     }
186
187     public void configure(Configuration configuration) throws ConfigurationException {
188         Domain.log("Configuring ClusterCacheRefresher", LOG_CHANNEL, Logger.INFO);
189
190         Enumeration JavaDoc nodes = configuration.getConfigurations("node");
191         while (nodes.hasMoreElements()) {
192             Configuration node = (Configuration) nodes.nextElement();
193             final String JavaDoc host = node.getAttribute("local-host");
194             final int port = node.getAttributeAsInt("local-port");
195             final String JavaDoc repositoryHost = node.getAttribute("repository-host");
196             final int repositoryPort = node.getAttributeAsInt("repository-port");
197             String JavaDoc repositoryProtocolString = node.getAttribute("repository-protocol", "http");
198             final Protocol protocol;
199             try {
200                 protocol = Protocol.getProtocol(repositoryProtocolString);
201             } catch (IllegalStateException JavaDoc exception) {
202                 throw new ConfigurationException("Unknown repository-protocol: " + repositoryProtocolString
203                         + ". Must be \"http\" or \"https\".", configuration);
204             }
205             String JavaDoc username = node.getAttribute("username", "");
206             String JavaDoc password = node.getAttribute("password", "");
207             final Credentials credentials = new UsernamePasswordCredentials(username, password);
208             final String JavaDoc repositoryDomain = node.getAttribute("repository-domain", "/slide");
209             final int pollInterval = node.getAttributeAsInt("poll-interval", 60000);
210             final boolean udp = node.getAttributeAsBoolean("udp", true);
211             final String JavaDoc uri = node.getAttribute("base-uri", "/");
212             final int depth = DepthSupport.DEPTH_INFINITY;
213             final int lifetime = node.getAttributeAsInt("subscription-lifetime", 3600);
214             final int notificationDelay = node.getAttributeAsInt("notification-delay", 0);
215
216             final Subscriber contentSubscriber = new Subscriber() {
217                 public void notify(String JavaDoc uri, Map JavaDoc information) {
218                     NamespaceAccessToken nat = Domain.accessNamespace(new SecurityToken(this), Domain.getDefaultNamespace());
219                     try {
220                         nat.begin();
221                         Iterator JavaDoc keys = information.keySet().iterator();
222                         while (keys.hasNext()) {
223                             String JavaDoc key = keys.next().toString();
224                             if ("uri".equals(key)) {
225                                 Uri theUri = nat.getUri(new SlideTokenImpl(new CredentialsToken("")), stripUri(information.get(key).toString()));
226                                 Store store = theUri.getStore();
227                                 if (store instanceof ExtendedStore) {
228                                     Domain.log("Resetting cache for " + theUri, LOG_CHANNEL, Logger.INFO);
229                                     ((ExtendedStore) store).removeObjectFromCache(theUri);
230                                 }
231                             }
232                         }
233                         nat.commit();
234                     } catch(Exception JavaDoc e) {
235                         if (Domain.isEnabled(LOG_CHANNEL, Logger.ERROR)) {
236                             Domain.log("Error clearing cache: " + e + ". See stderr for stacktrace.", LOG_CHANNEL, Logger.ERROR);
237                             e.printStackTrace();
238                         }
239                     }
240                 }
241             };
242
243             final Subscriber structureSubscriber = new Subscriber() {
244                 public void notify(String JavaDoc uri, Map JavaDoc information) {
245                     NamespaceAccessToken nat = Domain.accessNamespace(new SecurityToken(this), Domain.getDefaultNamespace());
246                     try {
247                         nat.begin();
248                         Iterator JavaDoc keys = information.keySet().iterator();
249                         while (keys.hasNext()) {
250                             String JavaDoc key = keys.next().toString();
251                             if ("uri".equals(key)) {
252                                 Uri theUri = nat.getUri(new SlideTokenImpl(new CredentialsToken("")), stripUri(information.get(key).toString()));
253                                 Store store = theUri.getParentUri().getStore();
254                                 if (store instanceof ExtendedStore) {
255                                     Domain.log("Resetting cache for " + theUri.getParentUri(), LOG_CHANNEL, Logger.INFO);
256                                     ((ExtendedStore) store).removeObjectFromCache(theUri.getParentUri());
257                                 }
258                             }
259                         }
260                         nat.commit();
261                     } catch(Exception JavaDoc e) {
262                         if (Domain.isEnabled(LOG_CHANNEL, Logger.ERROR)) {
263                             Domain.log("Error clearing cache: " + e + ". See stderr for stacktrace.", LOG_CHANNEL, Logger.ERROR);
264                             e.printStackTrace();
265                         }
266                     }
267                 }
268             };
269
270             /*
271              * This needs to be done in a thread for three reasons:
272              * 1) If NotificationListener.subscribe() connects to a Slide instance
273              * that is in the process of starting it will wait until the server
274              * has finished starting before it returns. If configuration is
275              * single-thread this prevents this Slide instance from starting
276              * until all other Slide instances have started. This means none
277              * of them can start if they're all waiting for each other. Simple
278              * test case of this is a cluster of one instance. It never starts.
279              * 2) Allows for renewing of subscriptions.
280              * 3) Allows for retrying failed subscriptions. This will happen if
281              * a server is down and NotificationListener.subscribe() can't
282              * reach it.
283              */

284             Thread JavaDoc t = new Thread JavaDoc(new Runnable JavaDoc() {
285                 
286                 private boolean success;
287                 
288                 public void run() {
289                     success = true;
290                     listener = new NotificationListener(host, port, repositoryHost, repositoryPort, protocol, credentials,
291                         repositoryDomain, pollInterval, udp);
292
293                     success = listener.subscribe("Update", uri, depth, lifetime, notificationDelay, contentSubscriber, credentials);
294                     success = listener.subscribe("Update/newmember", uri, depth, lifetime, notificationDelay, structureSubscriber, credentials);
295                     success = listener.subscribe("Delete", uri, depth, lifetime, notificationDelay, structureSubscriber, credentials);
296                     success = listener.subscribe("Move", uri, depth, lifetime, notificationDelay, structureSubscriber, credentials);
297                     
298                     if ( !success ) {
299                         // try again quickly
300
try {
301                             Thread.sleep(10000);
302                         } catch (InterruptedException JavaDoc e) {
303                             // ignore
304
}
305                     } else {
306                         // try again before the subscriptions expire
307
try {
308                             Thread.sleep(lifetime*1000-60);
309                         } catch (InterruptedException JavaDoc e) {
310                             // ignore
311
}
312                     }
313                 }
314             });
315             t.setDaemon(true);
316             t.start();
317         }
318     }
319     
320     /**
321      * Removes the first segment of a uri. "/slide/files/foo" becomes
322      * "/files/foo".
323      *
324      * @param uri the uri to strip
325      * @return the stipped uri
326      */

327     private String JavaDoc stripUri(String JavaDoc uri) {
328         // FIXME: if this is intended to remove the servlet path this will
329
// NOT work if the servlet is not default-servlet or is the root servlet
330
if ( uri.indexOf("/") == 0 ) {
331             uri = uri.substring(1);
332         }
333         if ( uri.indexOf("/") > -1 ) {
334             uri = uri.substring(uri.indexOf("/"));
335         }
336         return uri;
337     }
338 }
Popular Tags