1 /*
2  * Copyright (C) 2004-2008 Jive Software. All rights reserved.
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 package org.jivesoftware.util.cache;
17 
18 import org.jivesoftware.openfire.XMPPServer;
19 import org.jivesoftware.openfire.XMPPServerListener;
20 import org.jivesoftware.openfire.cluster.ClusterEventListener;
21 import org.jivesoftware.openfire.cluster.ClusterManager;
22 import org.jivesoftware.openfire.cluster.ClusterNodeInfo;
23 import org.jivesoftware.openfire.cluster.ClusterPacketRouter;
24 import org.jivesoftware.openfire.container.Plugin;
25 import org.jivesoftware.openfire.container.PluginClassLoader;
26 import org.jivesoftware.openfire.container.PluginManager;
27 import org.jivesoftware.openfire.session.RemoteSessionLocatorImpl;
28 import org.jivesoftware.util.InitializationException;
29 import org.jivesoftware.util.JiveConstants;
30 import org.jivesoftware.util.JiveGlobals;
31 import org.jivesoftware.util.PropertyEventDispatcher;
32 import org.jivesoftware.util.PropertyEventListener;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35 import org.xmpp.packet.JID;
36 
37 import java.net.URL;
38 import java.time.Duration;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.concurrent.ConcurrentHashMap;
47 import java.util.concurrent.TimeUnit;
48 import java.util.concurrent.locks.Lock;
49 
50 /**
51  * Creates Cache objects. The returned caches will either be local or clustered
52  * depending on the clustering enabled setting and a user's license.
53  *
54  * <p>When clustered caching is turned on, cache usage statistics for all caches
55  * that have been created are periodically published to the clustered cache
56  * named "opt-$cacheStats".</p>
57  *
58  */
59 @SuppressWarnings("rawtypes")
60 public class CacheFactory {
61 
62     private static final Logger log = LoggerFactory.getLogger(CacheFactory.class);
63 
64     public static String LOCAL_CACHE_PROPERTY_NAME = "cache.clustering.local.class";
65     public static String CLUSTERED_CACHE_PROPERTY_NAME = "cache.clustering.clustered.class";
66 
67     private static boolean clusteringStarted = false;
68     private static boolean clusteringStarting = false;
69 
70     /**
71      * Storage for all caches that get created.
72      */
73     private static Map<String, Cache> caches = new ConcurrentHashMap<>();
74     private static List<String> localOnly = Collections.synchronizedList(new ArrayList<String>());
75 
76     private static String localCacheFactoryClass;
77     private static String clusteredCacheFactoryClass;
78     private static CacheFactoryStrategy cacheFactoryStrategy = new DefaultLocalCacheStrategy();
79     private static CacheFactoryStrategy localCacheFactoryStrategy;
80     private static CacheFactoryStrategy clusteredCacheFactoryStrategy;
81     private static Thread statsThread;
82 
83     public static final int DEFAULT_MAX_CACHE_SIZE = 1024 * 256;
84     public static final long DEFAULT_MAX_CACHE_LIFETIME = 6 * JiveConstants.HOUR;
85 
86     /**
87      * This map contains property names which were used to store cache configuration data
88      * in local xml properties in previous versions.
89      */
90     private static final Map<String, String> cacheNames = new HashMap<>();
91     /**
92      * Default properties to use for local caches. Default properties can be overridden
93      * by setting the corresponding system properties.
94      */
95     private static final Map<String, Long> cacheProps = new HashMap<>();
96 
97     private static final String              PROPERTY_PREFIX_CACHE         = "cache.";
98 
99     private static final String              PROPERTY_SUFFIX_MAX_LIFE_TIME = ".maxLifetime";
100 
101     private static final String              PROPERTY_SUFFIX_SIZE          = ".size";
102 
103     private static final String              PROPERTY_SUFFIX_TYPE          = ".type";
104 
105     private static final String              PROPERTY_SUFFIX_MIN           = ".min";
106 
107     static {
108         localCacheFactoryClass = JiveGlobals.getProperty(LOCAL_CACHE_PROPERTY_NAME,
109                 "org.jivesoftware.util.cache.DefaultLocalCacheStrategy");
110         clusteredCacheFactoryClass = JiveGlobals.getProperty(CLUSTERED_CACHE_PROPERTY_NAME,
111                 "org.jivesoftware.openfire.plugin.util.cache.ClusteredCacheFactory");
112 
113         cacheNames.put("DNS Records", "dnsRecords");
114         cacheNames.put("Favicon Hits", "faviconHits");
115         cacheNames.put("Favicon Misses", "faviconMisses");
116         cacheNames.put("Group", "group");
117         cacheNames.put("Group Metadata Cache", "groupMeta");
118         cacheNames.put("Javascript Cache", "javascript");
119         cacheNames.put("Last Activity Cache", "lastActivity");
120         cacheNames.put("Multicast Service", "multicast");
121         cacheNames.put("Offline Message Size", "offlinemessage");
122         cacheNames.put("Offline Presence Cache", "offlinePresence");
123         cacheNames.put("Privacy Lists", "listsCache");
124         cacheNames.put("Remote Users Existence", "remoteUsersCache");
125         cacheNames.put("Roster", "username2roster");
126         cacheNames.put("RosterItems", "username2rosterItems");
127         cacheNames.put("User", "userCache");
128         cacheNames.put("Locked Out Accounts", "lockOutCache");
129         cacheNames.put("VCard", "vcardCache");
130         cacheNames.put("File Transfer Cache", "fileTransfer");
131         cacheNames.put("File Transfer", "transferProxy");
132         cacheNames.put("POP3 Authentication", "pop3");
133         cacheNames.put("LDAP Authentication", "ldap");
134         cacheNames.put("Routing Servers Cache", "routeServer");
135         cacheNames.put("Routing Components Cache", "routeComponent");
136         cacheNames.put("Routing Users Cache", "routeUser");
137         cacheNames.put("Routing AnonymousUsers Cache", "routeAnonymousUser");
138         cacheNames.put("Routing User Sessions", "routeUserSessions");
139         cacheNames.put("Routing Result Listeners", "routeResultListeners");
140         cacheNames.put("Components", "components");
141         cacheNames.put("Components Sessions", "componentsSessions");
142         cacheNames.put("Connection Managers Sessions", "connManagerSessions");
143         cacheNames.put("Incoming Server Sessions", "incServerSessions");
144         cacheNames.put("Sessions by Hostname", "sessionsHostname");
145         cacheNames.put("Secret Keys Cache", "secretKeys");
146         cacheNames.put("Validated Domains", "validatedDomains");
147         cacheNames.put("Directed Presences", "directedPresences");
148         cacheNames.put("Disco Server Features", "serverFeatures");
149         cacheNames.put("Disco Server Items", "serverItems");
150         cacheNames.put("Remote Server Configurations", "serversConfigurations");
151         cacheNames.put("Entity Capabilities", "entityCapabilities");
152         cacheNames.put("Entity Capabilities Users", "entityCapabilitiesUsers");
153         cacheNames.put("PEPServiceManager", "pepServiceManager");
154         cacheNames.put("Published Items", "publishedItems");
155         cacheNames.put("JID Node-parts", "jidNodeprep");
156         cacheNames.put("JID Domain-parts", "jidDomainprep");
157         cacheNames.put("JID Resource-parts", "jidResourceprep");
158         cacheNames.put("Sequences", "sequences");
159         cacheNames.put("MUC Service Pings Sent", "mucPings");
160 
161         cacheProps.put(PROPERTY_PREFIX_CACHE + "dnsRecords" + PROPERTY_SUFFIX_SIZE, 128 * 1024L);
162         cacheProps.put(PROPERTY_PREFIX_CACHE + "dnsRecords" + PROPERTY_SUFFIX_MAX_LIFE_TIME, 1000 * 60L);
163         cacheProps.put(PROPERTY_PREFIX_CACHE + "fileTransfer" + PROPERTY_SUFFIX_SIZE, 128 * 1024L);
164         cacheProps.put(PROPERTY_PREFIX_CACHE + "fileTransfer" + PROPERTY_SUFFIX_MAX_LIFE_TIME, 1000 * 60 * 10L);
165         cacheProps.put(PROPERTY_PREFIX_CACHE + "multicast" + PROPERTY_SUFFIX_SIZE, 128 * 1024L);
166         cacheProps.put(PROPERTY_PREFIX_CACHE + "multicast" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.DAY);
167         cacheProps.put(PROPERTY_PREFIX_CACHE + "offlinemessage" + PROPERTY_SUFFIX_SIZE, 100 * 1024L);
168         cacheProps.put(PROPERTY_PREFIX_CACHE + "offlinemessage" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.HOUR * 12);
169         cacheProps.put(PROPERTY_PREFIX_CACHE + "pop3" + PROPERTY_SUFFIX_SIZE, 512 * 1024L);
170         cacheProps.put(PROPERTY_PREFIX_CACHE + "pop3" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.HOUR);
171         cacheProps.put(PROPERTY_PREFIX_CACHE + "transferProxy" + PROPERTY_SUFFIX_SIZE, -1L);
172         cacheProps.put(PROPERTY_PREFIX_CACHE + "transferProxy" + PROPERTY_SUFFIX_MAX_LIFE_TIME, 1000 * 60 * 10L);
173         cacheProps.put(PROPERTY_PREFIX_CACHE + "group" + PROPERTY_SUFFIX_SIZE, 1024 * 1024L);
174         cacheProps.put(PROPERTY_PREFIX_CACHE + "group" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 15);
175         cacheProps.put(PROPERTY_PREFIX_CACHE + "lockOutCache" + PROPERTY_SUFFIX_SIZE, 1024 * 1024L);
176         cacheProps.put(PROPERTY_PREFIX_CACHE + "lockOutCache" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 15);
177         cacheProps.put(PROPERTY_PREFIX_CACHE + "groupMeta" + PROPERTY_SUFFIX_SIZE, 512 * 1024L);
178         cacheProps.put(PROPERTY_PREFIX_CACHE + "groupMeta" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 15);
179         cacheProps.put(PROPERTY_PREFIX_CACHE + "username2roster" + PROPERTY_SUFFIX_SIZE, 1024 * 1024L);
180         cacheProps.put(PROPERTY_PREFIX_CACHE + "username2roster" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 30);
181         cacheProps.put(PROPERTY_PREFIX_CACHE + "username2rosterItems" + PROPERTY_SUFFIX_SIZE, 1024 * 1024L);
182         cacheProps.put(PROPERTY_PREFIX_CACHE + "username2rosterItems" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 10);
183         cacheProps.put(PROPERTY_PREFIX_CACHE + "javascript" + PROPERTY_SUFFIX_SIZE, 128 * 1024L);
184         cacheProps.put(PROPERTY_PREFIX_CACHE + "javascript" + PROPERTY_SUFFIX_MAX_LIFE_TIME, 3600 * 24 * 10L);
185         cacheProps.put(PROPERTY_PREFIX_CACHE + "ldap" + PROPERTY_SUFFIX_SIZE, 512 * 1024L);
186         cacheProps.put(PROPERTY_PREFIX_CACHE + "ldap" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.HOUR * 2);
187         cacheProps.put(PROPERTY_PREFIX_CACHE + "listsCache" + PROPERTY_SUFFIX_SIZE, 512 * 1024L);
188         cacheProps.put(PROPERTY_PREFIX_CACHE + "offlinePresence" + PROPERTY_SUFFIX_SIZE, 512 * 1024L);
189         cacheProps.put(PROPERTY_PREFIX_CACHE + "lastActivity" + PROPERTY_SUFFIX_SIZE, 128 * 1024L);
190         cacheProps.put(PROPERTY_PREFIX_CACHE + "userCache" + PROPERTY_SUFFIX_SIZE, 512 * 1024L);
191         cacheProps.put(PROPERTY_PREFIX_CACHE + "userCache" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 30);
192         cacheProps.put(PROPERTY_PREFIX_CACHE + "remoteUsersCache" + PROPERTY_SUFFIX_SIZE, 512 * 1024L);
193         cacheProps.put(PROPERTY_PREFIX_CACHE + "remoteUsersCache" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 30);
194         cacheProps.put(PROPERTY_PREFIX_CACHE + "vcardCache" + PROPERTY_SUFFIX_SIZE, 512 * 1024L);
195         cacheProps.put(PROPERTY_PREFIX_CACHE + "faviconHits" + PROPERTY_SUFFIX_SIZE, 128 * 1024L);
196         cacheProps.put(PROPERTY_PREFIX_CACHE + "faviconMisses" + PROPERTY_SUFFIX_SIZE, 128 * 1024L);
197         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeServer" + PROPERTY_SUFFIX_SIZE, -1L);
198         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeServer" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
199         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeComponent" + PROPERTY_SUFFIX_SIZE, -1L);
200         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeComponent" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
201         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeUser" + PROPERTY_SUFFIX_SIZE, -1L);
202         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeUser" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
203         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeAnonymousUser" + PROPERTY_SUFFIX_SIZE, -1L);
204         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeAnonymousUser" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
205         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeUserSessions" + PROPERTY_SUFFIX_SIZE, -1L);
206         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeUserSessions" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
207         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeResultListeners" + PROPERTY_SUFFIX_SIZE, -1L);
208         cacheProps.put(PROPERTY_PREFIX_CACHE + "routeResultListeners" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
209         cacheProps.put(PROPERTY_PREFIX_CACHE + "components" + PROPERTY_SUFFIX_SIZE, -1L);
210         cacheProps.put(PROPERTY_PREFIX_CACHE + "components" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
211         cacheProps.put(PROPERTY_PREFIX_CACHE + "componentsSessions" + PROPERTY_SUFFIX_SIZE, -1L);
212         cacheProps.put(PROPERTY_PREFIX_CACHE + "componentsSessions" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
213         cacheProps.put(PROPERTY_PREFIX_CACHE + "connManagerSessions" + PROPERTY_SUFFIX_SIZE, -1L);
214         cacheProps.put(PROPERTY_PREFIX_CACHE + "connManagerSessions" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
215         cacheProps.put(PROPERTY_PREFIX_CACHE + "incServerSessions" + PROPERTY_SUFFIX_SIZE, -1L);
216         cacheProps.put(PROPERTY_PREFIX_CACHE + "incServerSessions" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
217         cacheProps.put(PROPERTY_PREFIX_CACHE + "sessionsHostname" + PROPERTY_SUFFIX_SIZE, -1L);
218         cacheProps.put(PROPERTY_PREFIX_CACHE + "sessionsHostname" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
219         cacheProps.put(PROPERTY_PREFIX_CACHE + "secretKeys" + PROPERTY_SUFFIX_SIZE, -1L);
220         cacheProps.put(PROPERTY_PREFIX_CACHE + "secretKeys" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
221         cacheProps.put(PROPERTY_PREFIX_CACHE + "validatedDomains" + PROPERTY_SUFFIX_SIZE, -1L);
222         cacheProps.put(PROPERTY_PREFIX_CACHE + "validatedDomains" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
223         cacheProps.put(PROPERTY_PREFIX_CACHE + "directedPresences" + PROPERTY_SUFFIX_SIZE, -1L);
224         cacheProps.put(PROPERTY_PREFIX_CACHE + "directedPresences" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
225         cacheProps.put(PROPERTY_PREFIX_CACHE + "serverFeatures" + PROPERTY_SUFFIX_SIZE, -1L);
226         cacheProps.put(PROPERTY_PREFIX_CACHE + "serverFeatures" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
227         cacheProps.put(PROPERTY_PREFIX_CACHE + "serverItems" + PROPERTY_SUFFIX_SIZE, -1L);
228         cacheProps.put(PROPERTY_PREFIX_CACHE + "serverItems" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
229         cacheProps.put(PROPERTY_PREFIX_CACHE + "serversConfigurations" + PROPERTY_SUFFIX_SIZE, 128 * 1024L);
230         cacheProps.put(PROPERTY_PREFIX_CACHE + "serversConfigurations" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 30);
231         cacheProps.put(PROPERTY_PREFIX_CACHE + "entityCapabilities" + PROPERTY_SUFFIX_SIZE, -1L);
232         cacheProps.put(PROPERTY_PREFIX_CACHE + "entityCapabilities" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.DAY * 2);
233         cacheProps.put(PROPERTY_PREFIX_CACHE + "entityCapabilitiesUsers" + PROPERTY_SUFFIX_SIZE, -1L);
234         cacheProps.put(PROPERTY_PREFIX_CACHE + "entityCapabilitiesUsers" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.DAY * 2);
235         cacheProps.put(PROPERTY_PREFIX_CACHE + "pluginCacheInfo" + PROPERTY_SUFFIX_SIZE, -1L);
236         cacheProps.put(PROPERTY_PREFIX_CACHE + "pluginCacheInfo" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
237         cacheProps.put(PROPERTY_PREFIX_CACHE + "pepServiceManager" + PROPERTY_SUFFIX_SIZE, 1024L * 1024 * 10);
238         cacheProps.put(PROPERTY_PREFIX_CACHE + "pepServiceManager" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 30);
239         cacheProps.put(PROPERTY_PREFIX_CACHE + "publishedItems" + PROPERTY_SUFFIX_SIZE, 1024L * 1024 * 10);
240         cacheProps.put(PROPERTY_PREFIX_CACHE + "publishedItems" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 15);
241         cacheProps.put(PROPERTY_PREFIX_CACHE + "sequences" + PROPERTY_SUFFIX_SIZE, -1L);
242         cacheProps.put(PROPERTY_PREFIX_CACHE + "sequences" + PROPERTY_SUFFIX_MAX_LIFE_TIME, -1L);
243         cacheProps.put(PROPERTY_PREFIX_CACHE + "mucPings" + PROPERTY_SUFFIX_SIZE, -1L);
244         cacheProps.put(PROPERTY_PREFIX_CACHE + "mucPings" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JiveConstants.MINUTE * 30);
245 
246         // The JID-based classes (wrappers for Caffeine caches) take their default values from whatever is hardcoded in the JID implementation.
247         cacheProps.put(PROPERTY_PREFIX_CACHE + "jidNodeprep" + PROPERTY_SUFFIX_SIZE, JID.NODEPREP_CACHE.policy().eviction().get().getMaximum() );
248         cacheProps.put(PROPERTY_PREFIX_CACHE + "jidNodeprep" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JID.NODEPREP_CACHE.policy().expireAfterWrite().get().getExpiresAfter( TimeUnit.MILLISECONDS ) );
249         cacheProps.put(PROPERTY_PREFIX_CACHE + "jidDomainprep" + PROPERTY_SUFFIX_SIZE, JID.DOMAINPREP_CACHE.policy().eviction().get().getMaximum() );
250         cacheProps.put(PROPERTY_PREFIX_CACHE + "jidDomainprep" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JID.DOMAINPREP_CACHE.policy().expireAfterWrite().get().getExpiresAfter( TimeUnit.MILLISECONDS ) );
251         cacheProps.put(PROPERTY_PREFIX_CACHE + "jidResourceprep" + PROPERTY_SUFFIX_SIZE, JID.RESOURCEPREP_CACHE.policy().eviction().get().getMaximum() );
252         cacheProps.put(PROPERTY_PREFIX_CACHE + "jidResourceprep" + PROPERTY_SUFFIX_MAX_LIFE_TIME, JID.RESOURCEPREP_CACHE.policy().expireAfterWrite().get().getExpiresAfter( TimeUnit.MILLISECONDS ) );
253 
PropertyEventDispatcher.addListener( new PropertyEventListener() { @Override public void propertySet( String property, Map<String, Object> params ) { final Cache cache = getCacheByProperty( property ); if ( cache == null ) { return; } if (property.endsWith(PROPERTY_SUFFIX_SIZE)) { final long size = getMaxCacheSize( cache.getName() ); cache.setMaxCacheSize( size ); } if (property.endsWith(PROPERTY_SUFFIX_MAX_LIFE_TIME)) { final long lifetime = getMaxCacheLifetime( cache.getName() ); cache.setMaxLifetime( lifetime ); } } @Override public void propertyDeleted( String property, Map<String, Object> params ) { propertySet( property, params ); } @Override public void xmlPropertySet( String property, Map<String, Object> params ) { propertySet( property, params ); } @Override public void xmlPropertyDeleted( String property, Map<String, Object> params ) { propertySet( property, params ); } } )254         PropertyEventDispatcher.addListener( new PropertyEventListener()
255         {
256 
257             @Override
258             public void propertySet( String property, Map<String, Object> params )
259             {
260                 final Cache cache = getCacheByProperty( property );
261                 if ( cache == null )
262                 {
263                     return;
264                 }
265 
266                 if (property.endsWith(PROPERTY_SUFFIX_SIZE))
267                 {
268                     final long size = getMaxCacheSize( cache.getName() );
269                     cache.setMaxCacheSize( size );
270                 }
271 
272                 if (property.endsWith(PROPERTY_SUFFIX_MAX_LIFE_TIME))
273                 {
274                     final long lifetime = getMaxCacheLifetime( cache.getName() );
275                     cache.setMaxLifetime( lifetime );
276                 }
277 
278                 // Note that changes to 'min' and 'type' cannot be applied runtime - a restart is required for those.
279             }
280 
281             @Override
282             public void propertyDeleted( String property, Map<String, Object> params )
283             {
284                 propertySet( property, params );
285             }
286 
287             @Override
288             public void xmlPropertySet( String property, Map<String, Object> params )
289             {
290                 propertySet( property, params );
291             }
292 
293             @Override
294             public void xmlPropertyDeleted( String property, Map<String, Object> params )
295             {
296                 propertySet( property, params );
297             }
298         } );
299     }
300 
301     // this field is unused, but retains a reference that intends to prevent the monitor from being garbage collected.
302     @SuppressWarnings("unused")
303     private static ConsistencyMonitor consistencyMonitor;
304 
CacheFactory()305     private CacheFactory() {
306     }
307 
308     /**
309      * If a local property is found for the supplied name which specifies a value for cache size, it is returned.
310      * Otherwise, the defaultSize argument is returned.
311      *
312      * @param cacheName the name of the cache to look up a corresponding property for.
313      * @return either the property value or the default value.
314      */
getMaxCacheSize(String cacheName)315     public static long getMaxCacheSize(String cacheName) {
316         return getCacheProperty(cacheName, PROPERTY_SUFFIX_SIZE, DEFAULT_MAX_CACHE_SIZE);
317     }
318 
319     /**
320      * Sets a local property which overrides the maximum cache size for the
321      * supplied cache name.
322      * @param cacheName the name of the cache to store a value for.
323      * @param size the maximum cache size.
324      */
setMaxSizeProperty(String cacheName, long size)325     public static void setMaxSizeProperty(String cacheName, long size) {
326         cacheName = cacheName.replaceAll(" ", "");
327         if ( !Long.toString(size).equals(JiveGlobals.getProperty(PROPERTY_PREFIX_CACHE + cacheName + PROPERTY_SUFFIX_SIZE)))
328         {
329             JiveGlobals.setProperty(PROPERTY_PREFIX_CACHE + cacheName + PROPERTY_SUFFIX_SIZE, Long.toString(size));
330         }
331     }
332 
hasMaxSizeFromProperty(String cacheName)333     public static boolean hasMaxSizeFromProperty(String cacheName) {
334         return hasCacheProperty(cacheName, PROPERTY_SUFFIX_SIZE);
335     }
336 
337     /**
338     * If a local property is found for the supplied name which specifies a value for cache entry lifetime, it
339      * is returned. Otherwise, the defaultLifetime argument is returned.
340      *
341     * @param cacheName the name of the cache to look up a corresponding property for.
342     * @return either the property value or the default value.
343     */
getMaxCacheLifetime(String cacheName)344     public static long getMaxCacheLifetime(String cacheName) {
345         return getCacheProperty(cacheName, PROPERTY_SUFFIX_MAX_LIFE_TIME, DEFAULT_MAX_CACHE_LIFETIME);
346     }
347 
348     /**
349      * Sets a local property which overrides the maximum cache entry lifetime
350      * for the supplied cache name.
351      * @param cacheName the name of the cache to store a value for.
352      * @param lifetime the maximum cache entry lifetime.
353      */
setMaxLifetimeProperty(String cacheName, long lifetime)354     public static void setMaxLifetimeProperty(String cacheName, long lifetime) {
355         cacheName = cacheName.replaceAll(" ", "");
356         if ( !Long.toString(lifetime).equals(JiveGlobals.getProperty(PROPERTY_PREFIX_CACHE + cacheName + PROPERTY_SUFFIX_MAX_LIFE_TIME)))
357         {
358             JiveGlobals.setProperty((PROPERTY_PREFIX_CACHE + cacheName + PROPERTY_SUFFIX_MAX_LIFE_TIME), Long.toString(lifetime));
359         }
360     }
361 
hasMaxLifetimeFromProperty(String cacheName)362     public static boolean hasMaxLifetimeFromProperty(String cacheName) {
363         return hasCacheProperty(cacheName, PROPERTY_SUFFIX_MAX_LIFE_TIME);
364     }
365 
setCacheTypeProperty(String cacheName, String type)366     public static void setCacheTypeProperty(String cacheName, String type) {
367         cacheName = cacheName.replaceAll(" ", "");
368         if ( !type.equals(JiveGlobals.getProperty(PROPERTY_PREFIX_CACHE + cacheName + PROPERTY_SUFFIX_TYPE)))
369         {
370             JiveGlobals.setProperty(PROPERTY_PREFIX_CACHE + cacheName + PROPERTY_SUFFIX_TYPE, type);
371         }
372     }
373 
getCacheTypeProperty(String cacheName)374     public static String getCacheTypeProperty(String cacheName) {
375         cacheName = cacheName.replaceAll(" ", "");
376         return JiveGlobals.getProperty(PROPERTY_PREFIX_CACHE + cacheName + PROPERTY_SUFFIX_TYPE);
377     }
378 
setMinCacheSize(String cacheName, long size)379     public static void setMinCacheSize(String cacheName, long size) {
380         cacheName = cacheName.replaceAll(" ", "");
381         if ( !Long.toString(size).equals(JiveGlobals.getProperty(PROPERTY_PREFIX_CACHE + cacheName + PROPERTY_SUFFIX_MIN)))
382         {
383             JiveGlobals.setProperty(PROPERTY_PREFIX_CACHE + cacheName + PROPERTY_SUFFIX_MIN, Long.toString(size));
384         }
385     }
386 
getMinCacheSize(String cacheName)387     public static long getMinCacheSize(String cacheName) {
388         return getCacheProperty(cacheName, PROPERTY_SUFFIX_MIN, 0);
389     }
390 
getCacheByProperty( String property )391     private static Cache getCacheByProperty( String property )
392     {
393         if ( !property.startsWith(PROPERTY_PREFIX_CACHE))
394         {
395             return null;
396         }
397 
398         // Extract the cache name identifier from the property name.
399         final String name = property.substring(PROPERTY_PREFIX_CACHE.length(), property.lastIndexOf("."));
400 
401         // See if property is using the short name variant.
402         for ( final Map.Entry<String, String> entry : cacheNames.entrySet() )
403         {
404             if ( name.equals( entry.getValue() ) )
405             {
406                 return caches.get( entry.getKey() );
407             }
408         }
409 
410         // If not a short name, then try for a normalized name.
411         for ( final Map.Entry<String, Cache> entry : caches.entrySet() )
412         {
413             if ( entry.getKey().replaceAll(" ", "").equals( name ) )
414             {
415                 return entry.getValue();
416             }
417         }
418 
419         return null;
420     }
421 
getCacheProperty(String cacheName, String suffix, long defaultValue)422     private static long getCacheProperty(String cacheName, String suffix, long defaultValue) {
423         // First check if user is overwriting default value using a system property for the cache name
424         String propName = PROPERTY_PREFIX_CACHE + cacheName.replaceAll(" ", "") + suffix;
425         String sizeProp = JiveGlobals.getProperty(propName);
426         if (sizeProp == null && cacheNames.containsKey(cacheName)) {
427             // No system property was found for the cache name so try now with short name
428             propName = PROPERTY_PREFIX_CACHE + cacheNames.get(cacheName) + suffix;
429             sizeProp = JiveGlobals.getProperty(propName);
430         }
431         if (sizeProp != null) {
432             try {
433                 return Long.parseLong(sizeProp);
434             }
435             catch (NumberFormatException nfe) {
436                 log.warn("Unable to parse " + propName + " using default value.");
437             }
438         }
439         // Check if there is a default size value for this cache
440         Long defaultSize = cacheProps.get(propName);
441         return defaultSize == null ? defaultValue : defaultSize;
442     }
443 
hasCacheProperty(String cacheName, String suffix)444     private static boolean hasCacheProperty(String cacheName, String suffix) {
445         // First check if user is overwriting default value using a system property for the cache name
446         String propName = PROPERTY_PREFIX_CACHE + cacheName.replaceAll(" ", "") + suffix;
447         String sizeProp = JiveGlobals.getProperty(propName);
448         if (sizeProp == null && cacheNames.containsKey(cacheName)) {
449             // No system property was found for the cache name so try now with short name
450             propName = PROPERTY_PREFIX_CACHE + cacheNames.get(cacheName) + suffix;
451             sizeProp = JiveGlobals.getProperty(propName);
452         }
453         if (sizeProp != null) {
454             try {
455                 Long.parseLong(sizeProp);
456                 return true;
457             }
458             catch (NumberFormatException nfe) {
459                 log.warn("Unable to parse " + propName + " using default value.");
460             }
461         }
462         return false;
463     }
464 
465     /**
466      * Returns an array of all caches in the system.
467      * @return an array of all caches in the system.
468      */
getAllCaches()469     public static Cache[] getAllCaches() {
470         List<Cache> values = new ArrayList<>();
471         for (Cache cache : caches.values()) {
472             values.add(cache);
473         }
474         return values.toArray(new Cache[values.size()]);
475     }
476 
477     /**
478      * Returns the named cache, creating it as necessary.
479      *
480      * @param name         the name of the cache to create.
481      * @param <T> the type cache being created
482      * @return the named cache, creating it as necessary.
483      */
484     @SuppressWarnings("unchecked")
createCache(String name)485     public static synchronized <T extends Cache> T createCache(String name) {
486         T cache = (T) caches.get(name);
487         if (cache != null) {
488             return cache;
489         }
490         cache = (T) cacheFactoryStrategy.createCache(name);
491 
492         log.info("Created cache [" + cacheFactoryStrategy.getClass().getName() + "] for " + name);
493 
494         return wrapCache(cache, name);
495     }
496 
497     /**
498      * Returns the serializing cache, creating it as necessary. Unlike the caches returned by
499      * {@link #createCache(String)}, the caches returned by this method store data in serialized form (without a
500      * reference to their class).
501      *
502      * The primary benefit of usage of this cache is that the cached data is stored without any references to their
503      * classes. This allows cache content to remain usable after the classes that instantiate the data get reloaded.
504      * This is of particular interest when the cache is used to store data provided by Openfire plugins (as these
505      * classes get loaded by a class loader that is replaced when a plugin gets reloaded or upgraded).
506      *
507      * As compared to other caches, usage of this cache will require more system resources, as the serialized
508      * representation of an object typically is (much) larger than its original (unserialized) form.
509      *
510      * @param name       The name of the cache to create.
511      * @param keyClass   The class of instances used as keys.
512      * @param valueClass The class of instances used as values.
513      * @return the named cache, creating it as necessary.
514      * @see <a href="https://igniterealtime.atlassian.net/browse/OF-2239">Issue OF-2239: Make it easier to cache plugin class instances</a>
515      */
516     @SuppressWarnings("unchecked")
createSerializingCache(String name, Class keyClass, Class valueClass)517     public static synchronized SerializingCache createSerializingCache(String name, Class keyClass, Class valueClass) {
518         SerializingCache cache = (SerializingCache) caches.get(name);
519         if (cache != null) {
520             return cache;
521         }
522 
523         final Cache<String, String> delegate = (Cache<String, String>) cacheFactoryStrategy.createCache(name);
524         cache = new SerializingCache(delegate, keyClass, valueClass);
525 
526         log.info("Created serializing cache [" + cacheFactoryStrategy.getClass().getName() + "] for " + name);
527 
528         return wrapCache(cache, name);
529     }
530 
531     /**
532      * Returns the named local cache, creating it as necessary.
533      *
534      * @param name         the name of the cache to create.
535      * @param <T> the type cache being created
536      * @return the named cache, creating it as necessary.
537      */
538     @SuppressWarnings("unchecked")
createLocalCache(String name)539     public static synchronized <T extends Cache> T createLocalCache(String name) {
540         T cache = (T) caches.get(name);
541         if (cache != null) {
542             return cache;
543         }
544         cache = (T) localCacheFactoryStrategy.createCache(name);
545         localOnly.add(name);
546 
547         log.info("Created local-only cache [" + localCacheFactoryClass + "] for " + name);
548 
549         return wrapCache(cache, name);
550     }
551 
552     /**
553      * Destroys the cache for the cache name specified.
554      *
555      * @param name the name of the cache to destroy.
556      */
destroyCache(String name)557     public static synchronized void destroyCache(String name) {
558         Cache cache = caches.remove(name);
559         if (cache != null) {
560             if (localOnly.contains(name)) {
561                 localOnly.remove(name);
562                 localCacheFactoryStrategy.destroyCache(cache);
563             } else {
564                 cacheFactoryStrategy.destroyCache(cache);
565             }
566         }
567     }
568 
569     /**
570      * @deprecated in favour of {@link Cache#getLock}. Will be removed in Openfire 5.0.0.
571      *
572      * <p>Returns an existing {@link java.util.concurrent.locks.Lock} on the specified key or creates a new one
573      * if none was found. This operation is thread safe. Successive calls with the same key may or may not
574      * return the same {@link java.util.concurrent.locks.Lock}. However, different threads asking for the
575      * same Lock at the same time will get the same Lock object.<p>
576      *
577      * The supplied cache may or may not be used depending whether the server is running on cluster mode
578      * or not. When not running as part of a cluster then the lock will be unrelated to the cache and will
579      * only be visible in this JVM.
580      *
581      * @param key the object that defines the visibility or scope of the lock.
582      * @param cache the cache used for holding the lock.
583      * @return an existing lock on the specified key or creates a new one if none was found.
584      */
585     @Deprecated
getLock(Object key, Cache cache)586     public static synchronized Lock getLock(Object key, Cache cache) {
587         if (localOnly.contains(cache.getName())) {
588             return localCacheFactoryStrategy.getLock(key, cache);
589         } else {
590             return cacheFactoryStrategy.getLock(key, cache);
591         }
592     }
593 
594     @SuppressWarnings("unchecked")
wrapCache(T cache, String name)595     private static <T extends Cache> T wrapCache(T cache, String name) {
596         if ("Routing Components Cache".equals(name)) {
597             cache = (T) new ComponentCacheWrapper(cache);
598         } else {
599             cache = (T) new CacheWrapper(cache);
600         }
601         cache.setName(name);
602 
603         caches.put(name, cache);
604         return cache;
605     }
606 
607     /**
608      * Returns true if clustering is installed and can be used by this JVM
609      * to join a cluster. A false value could mean that either clustering
610      * support is not available or the license does not allow to have more
611      * than 1 cluster node.
612      *
613      * @return true if clustering is installed and can be used by
614      * this JVM to join a cluster.
615      */
isClusteringAvailable()616     public static boolean isClusteringAvailable() {
617         if (clusteredCacheFactoryStrategy == null) {
618             try {
619                 clusteredCacheFactoryStrategy = (CacheFactoryStrategy) Class.forName(
620                         clusteredCacheFactoryClass, true,
621                         getClusteredCacheStrategyClassLoader()).newInstance();
622             } catch (NoClassDefFoundError | Exception e) {
623                 log.warn("Clustered cache factory strategy " + clusteredCacheFactoryClass + " not found");
624             }
625         }
626         return (clusteredCacheFactoryStrategy != null);
627     }
628 
629     /**
630      * Returns true is clustering is currently being started. Once the cluster
631      * is started or failed to be started this value will be false.
632      *
633      * @return true is clustering is currently being started.
634      */
isClusteringStarting()635     public static boolean isClusteringStarting() {
636         return clusteringStarting;
637     }
638 
639     /**
640      * Returns true if this node is currently a member of a cluster. The last step of application
641      * initialization is to join a cluster, so this method returns false during most of application startup.
642      *
643      * @return true if this node is currently a member of a cluster.
644      */
isClusteringStarted()645     public static boolean isClusteringStarted() {
646         return clusteringStarted;
647     }
648 
649     /**
650      * Returns a byte[] that uniquely identifies this member within the cluster or {@code null}
651      * when not in a cluster.
652      *
653      * @return a byte[] that uniquely identifies this member within the cluster or null when not in a cluster.
654      */
getClusterMemberID()655     public static byte[] getClusterMemberID() {
656         return cacheFactoryStrategy.getClusterMemberID();
657     }
658 
clearCaches()659     public synchronized static void clearCaches() {
660         for (String cacheName : caches.keySet()) {
661             Cache cache = caches.get(cacheName);
662             cache.clear();
663         }
664     }
665 
clearCaches( String... cacheName )666     public synchronized static void clearCaches( String... cacheName )
667     {
668         caches.values().parallelStream()
669             .filter(cache -> Arrays.asList(cacheName).contains(cache.getName()))
670             .forEach(Map::clear);
671     }
672 
673     /**
674      * Returns a byte[] that uniquely identifies this senior cluster member or {@code null}
675      * when not in a cluster.
676      *
677      * @return a byte[] that uniquely identifies this senior cluster member or null when not in a cluster.
678      */
getSeniorClusterMemberID()679     public static byte[] getSeniorClusterMemberID() {
680         return cacheFactoryStrategy.getSeniorClusterMemberID();
681     }
682 
683     /**
684      * Returns true if this member is the senior member in the cluster. If clustering
685      * is not enabled, this method will also return true. This test is useful for
686      * tasks that should only be run on a single member in a cluster.
687      *
688      * @return true if this cluster member is the senior or if clustering is not enabled.
689      */
isSeniorClusterMember()690     public static boolean isSeniorClusterMember() {
691         return cacheFactoryStrategy.isSeniorClusterMember();
692     }
693 
694     /**
695      * Returns basic information about the current members of the cluster or an empty
696      * collection if not running in a cluster.
697      *
698      * @return information about the current members of the cluster or an empty
699      *         collection if not running in a cluster.
700      */
getClusterNodesInfo()701     public static Collection<ClusterNodeInfo> getClusterNodesInfo() {
702         return cacheFactoryStrategy.getClusterNodesInfo();
703     }
704 
705     /**
706      * Returns the maximum number of cluster members allowed. A value of 0 will
707      * be returned when clustering is not allowed.
708      *
709      * @return the maximum number of cluster members allowed or 0 if clustering is not allowed.
710      */
getMaxClusterNodes()711     public static int getMaxClusterNodes() {
712         return cacheFactoryStrategy.getMaxClusterNodes();
713     }
714 
715     /**
716      * Gets the pseudo-synchronized time from the cluster. While the cluster members may
717      * have varying system times, this method is expected to return a timestamp that is
718      * synchronized (or nearly so; best effort) across the cluster.
719      *
720      * @return Synchronized time for all cluster members
721      */
getClusterTime()722     public static long getClusterTime() {
723         // use try/catch here for backward compatibility with older plugin(s)
724         try { return cacheFactoryStrategy.getClusterTime(); }
725         catch (AbstractMethodError ame) {
726             log.warn("Cluster time not available; check for update to hazelcast/clustering plugin");
727             return localCacheFactoryStrategy.getClusterTime();
728         }
729     }
730 
731     /**
732      * Invokes a task on other cluster members in an asynchronous fashion. The task will not be
733      * executed on the local cluster member. If clustering is not enabled, this method
734      * will do nothing.
735      *
736      * @param task the task to be invoked on all other cluster members.
737      */
doClusterTask(final ClusterTask<?> task)738     public static void doClusterTask(final ClusterTask<?> task) {
739         cacheFactoryStrategy.doClusterTask(task);
740     }
741 
742     /**
743      * Invokes a task on a given cluster member in an asynchronous fashion. If clustering is not enabled,
744      * this method will do nothing.
745      *
746      * @param task the task to be invoked on the specified cluster member.
747      * @param nodeID the byte array that identifies the target cluster member.
748      * @throws IllegalStateException if requested node was not found or not running in a cluster.
749      */
doClusterTask(final ClusterTask<?> task, byte[] nodeID)750     public static void doClusterTask(final ClusterTask<?> task, byte[] nodeID) {
751         cacheFactoryStrategy.doClusterTask(task, nodeID);
752     }
753 
754     /**
755      * Invokes a task on other cluster members synchronously and returns the result as a Collection
756      * (method will not return until the task has been executed on each cluster member).
757      * The task will not be executed on the local cluster member. If clustering is not enabled,
758      * this method will return an empty collection.
759      *
760      * @param task               the ClusterTask object to be invoked on all other cluster members.
761      * @param includeLocalMember true to run the task on the local member, false otherwise
762      * @param <T> the return type of the cluster task
763      * @return collection with the result of the execution.
764      */
doSynchronousClusterTask(ClusterTask<T> task, boolean includeLocalMember)765     public static <T> Collection<T> doSynchronousClusterTask(ClusterTask<T> task, boolean includeLocalMember) {
766         return cacheFactoryStrategy.doSynchronousClusterTask(task, includeLocalMember);
767     }
768 
769     /**
770      * Invokes a task on a given cluster member synchronously and returns the result of
771      * the remote operation. If clustering is not enabled, this method will return null.
772      *
773      * @param task        the ClusterTask object to be invoked on a given cluster member.
774      * @param nodeID      the byte array that identifies the target cluster member.
775      * @param <T> the return type of the cluster task
776      * @return result of remote operation or null if operation failed or operation returned null.
777      * @throws IllegalStateException if requested node was not found or not running in a cluster.
778      */
doSynchronousClusterTask(ClusterTask<T> task, byte[] nodeID)779     public static <T> T doSynchronousClusterTask(ClusterTask<T> task, byte[] nodeID) {
780         return cacheFactoryStrategy.doSynchronousClusterTask(task, nodeID);
781     }
782 
783     /**
784      * Returns the node info for the given cluster node
785      * @param nodeID The target cluster node
786      * @return The info for the cluster node or null if not found
787      */
getClusterNodeInfo(byte[] nodeID)788     public static ClusterNodeInfo getClusterNodeInfo(byte[] nodeID) {
789         return cacheFactoryStrategy.getClusterNodeInfo(nodeID);
790     }
791 
getPluginName()792     public static String getPluginName() {
793         return cacheFactoryStrategy.getPluginName();
794     }
795 
initialize()796     public static synchronized void initialize() throws InitializationException {
797         try {
798             localCacheFactoryStrategy = (CacheFactoryStrategy) Class.forName(localCacheFactoryClass).newInstance();
799             cacheFactoryStrategy = localCacheFactoryStrategy;
800 
801             // Update the JID-internal caches, if they're configured differently than their default.
802             JID.NODEPREP_CACHE.policy().eviction().get().setMaximum( getMaxCacheSize( "jidNodeprep" ) );
803             JID.NODEPREP_CACHE.policy().expireAfterWrite().get().setExpiresAfter( getMaxCacheLifetime( "jidNodeprep" ), TimeUnit.MILLISECONDS );
804             JID.DOMAINPREP_CACHE.policy().eviction().get().setMaximum( getMaxCacheSize( "jidDomainprep" ) );
805             JID.DOMAINPREP_CACHE.policy().expireAfterWrite().get().setExpiresAfter( getMaxCacheLifetime( "jidDomainprep" ), TimeUnit.MILLISECONDS );
806             JID.RESOURCEPREP_CACHE.policy().eviction().get().setMaximum( getMaxCacheSize( "jidResourceprep" ) );
807             JID.RESOURCEPREP_CACHE.policy().expireAfterWrite().get().setExpiresAfter( getMaxCacheLifetime( "jidResourceprep" ), TimeUnit.MILLISECONDS );
808 
809             // Mock cache creation for the JID-internal classes, by wrapping them in a compatibility layer.
810             caches.put("JID Node-parts", CaffeineCache.of( JID.NODEPREP_CACHE, "JID Node-parts" ));
811             caches.put("JID Domain-parts", CaffeineCache.of( JID.DOMAINPREP_CACHE, "JID Domain-parts" ));
812             caches.put("JID Resource-parts", CaffeineCache.of( JID.RESOURCEPREP_CACHE, "JID Resource-parts" ));
813 
814         } catch (Exception e) {
815             log.error("Failed to instantiate local cache factory strategy: " + localCacheFactoryClass, e);
816              throw new InitializationException(e);
817         }
818 
819         consistencyMonitor = ConsistencyMonitor.getInstance();
820     }
821 
getClusteredCacheStrategyClassLoader()822     private static ClassLoader getClusteredCacheStrategyClassLoader() {
823         PluginManager pluginManager = XMPPServer.getInstance().getPluginManager();
824         Plugin plugin = pluginManager.getPlugin("hazelcast");
825         if (plugin == null) {
826             plugin = pluginManager.getPlugin("clustering");
827             if (plugin == null) {
828                 plugin = pluginManager.getPlugin("enterprise");
829             }
830         }
831         PluginClassLoader pluginLoader = pluginManager.getPluginClassloader(plugin);
832         if (pluginLoader != null) {
833             if (log.isDebugEnabled()) {
834                 StringBuffer pluginLoaderDetails = new StringBuffer("Clustering plugin class loader: ");
835                 pluginLoaderDetails.append(pluginLoader.getClass().getName());
836                 for (URL url : pluginLoader.getURLs()) {
837                     pluginLoaderDetails.append("\n\t").append(url.toExternalForm());
838                 }
839                 log.debug(pluginLoaderDetails.toString());
840             }
841             return pluginLoader;
842         }
843         else {
844             log.warn("CacheFactory - Unable to find a Plugin that provides clustering support.");
845             return Thread.currentThread().getContextClassLoader();
846         }
847     }
848 
startClustering()849     public static void startClustering() {
850         if (isClusteringAvailable()) {
851             clusteringStarting = true;
852             // Set session locator to use when in a cluster
853             XMPPServer.getInstance().setRemoteSessionLocator(new RemoteSessionLocatorImpl());
854             // Set packet router to use to deliver packets to remote cluster nodes
855             XMPPServer.getInstance().getRoutingTable().setRemotePacketRouter(new ClusterPacketRouter());
856             clusteringStarted = clusteredCacheFactoryStrategy.startCluster();
857             clusteringStarting = false;
858         }
859         if (clusteringStarted) {
860             if (statsThread == null) {
861                 // Start a timing thread with 1 second of accuracy.
862                 statsThread = new Thread("Cache Stats") {
863                     private volatile boolean destroyed = false;
864 
865                     @Override
866                     public void run() {
867                         XMPPServer.getInstance().addServerListener(new XMPPServerListener() {
868                             @Override
869                             public void serverStarted() {}
870 
871                             @Override
872                             public void serverStopping() {
873                                 destroyed = true;
874                             }
875                         });
876                         ClusterManager.addListener(new ClusterEventListener() {
877                             @Override
878                             public void joinedCluster() {}
879 
880                             @Override
881                             public void joinedCluster(byte[] nodeID) {}
882 
883                             @Override
884                             public void leftCluster() {
885                                 destroyed = true;
886                                 ClusterManager.removeListener(this);
887                                 log.debug("CacheFactory is not listening for cluster events anymore because it left the cluster");
888                             }
889 
890                             @Override
891                             public void leftCluster(byte[] nodeID) {}
892 
893                             @Override
894                             public void markedAsSeniorClusterMember() {}
895                         });
896 
897                         // Run the timer indefinitely.
898                         while (!destroyed && ClusterManager.isClusteringEnabled()) {
899                             // Publish cache stats for this cluster node (assuming clustering is
900                             // enabled and there are stats to publish).
901                             try {
902                                 cacheFactoryStrategy.updateCacheStats(caches);
903                             }
904                             catch (Exception e) {
905                                 log.error(e.getMessage(), e);
906                             }
907                             try {
908                                 // Sleep 10 seconds.
909                                 sleep(10000);
910                             }
911                             catch (InterruptedException ie) {
912                                 // Ignore.
913                             }
914                         }
915                         statsThread = null;
916                         log.debug("Cache stats thread terminated.");
917                     }
918                 };
919                 statsThread.setDaemon(true);
920                 statsThread.start();
921             }
922         }
923     }
924 
stopClustering()925     public static void stopClustering() {
926         // Stop the cluster
927         clusteredCacheFactoryStrategy.stopCluster();
928         clusteredCacheFactoryStrategy = null;
929         XMPPServer.getInstance().setRemoteSessionLocator(null);
930         XMPPServer.getInstance().getRoutingTable().setRemotePacketRouter(null);
931         // Set the strategy to local
932         cacheFactoryStrategy = localCacheFactoryStrategy;
933     }
934 
935     /**
936      * Notification message indicating that this JVM has joined a cluster.
937      */
938     @SuppressWarnings("unchecked")
joinedCluster()939     public static synchronized void joinedCluster() {
940         cacheFactoryStrategy = clusteredCacheFactoryStrategy;
941         // Loop through local caches and switch them to clustered cache (purge content)
942         Arrays.stream(getAllCaches())
943             .filter(CacheFactory::isClusterableCache)
944             .forEach(cache -> {
945                 final CacheWrapper cacheWrapper = ((CacheWrapper) cache);
946                 final Cache clusteredCache = cacheFactoryStrategy.createCache(cacheWrapper.getName());
947                 cacheWrapper.setWrappedCache(clusteredCache);
948             });
949         clusteringStarting = false;
950         clusteringStarted = true;
951         log.info("Clustering started; cache migration complete");
952     }
953 
954     /**
955      * Notification message indicating that this JVM has left the cluster.
956      */
957     @SuppressWarnings("unchecked")
leftCluster()958     public static synchronized void leftCluster() {
959         clusteringStarted = false;
960         cacheFactoryStrategy = localCacheFactoryStrategy;
961 
962         // Loop through clustered caches and change them to local caches (purge content)
963         Arrays.stream(getAllCaches())
964             .filter(CacheFactory::isClusterableCache)
965             .forEach(cache -> {
966                 final CacheWrapper cacheWrapper = ((CacheWrapper) cache);
967                 final Cache standaloneCache = cacheFactoryStrategy.createCache(cacheWrapper.getName());
968                 cacheWrapper.setWrappedCache(standaloneCache);
969             });
970         log.info("Clustering stopped; cache migration complete");
971     }
972 
973     /**
974      * Indicates if the supplied Cache is "clusterable". This is used to determine if a cache should be migrated
975      * between a {@link DefaultCache} and a clustered cache when the node joins/leaves the cluster.
976      * <p>
977      * A cache is considered 'clusterable' if;
978      * <ul>
979      *     <li>the cache is not a 'local' cache - which apply to the local node only so do not need to be clustered, and</li>
980      *     <li>the cache is actually a {@link CacheWrapper} which wraps the underlying default or clustered cache</li>
981      * </ul>
982      *
983      * @param cache the cache to check
984      * @return {@code true} if the cache can be converted to/from a clustered cache, otherwise {@code false}
985      */
isClusterableCache(final Cache cache)986     private static boolean isClusterableCache(final Cache cache) {
987         return cache instanceof CacheWrapper && !localOnly.contains(cache.getName());
988     }
989 
990 }
991