CacheFactory.java 34.1 KB
Newer Older
1
/**
2 3 4
 * $RCSfile$
 * $Revision: 3144 $
 * $Date: 2005-12-01 14:20:11 -0300 (Thu, 01 Dec 2005) $
5
 *
6 7
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
 *
8 9 10 11 12 13 14 15 16 17 18
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
19 20 21
 */
package org.jivesoftware.util.cache;

22
import java.net.URL;
23 24
import java.util.ArrayList;
import java.util.Collection;
25
import java.util.Collections;
26 27 28 29 30 31
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;

32 33
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.XMPPServerListener;
Gaston Dombiak's avatar
Gaston Dombiak committed
34 35
import org.jivesoftware.openfire.cluster.ClusterEventListener;
import org.jivesoftware.openfire.cluster.ClusterManager;
Gaston Dombiak's avatar
Gaston Dombiak committed
36
import org.jivesoftware.openfire.cluster.ClusterNodeInfo;
37 38
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginClassLoader;
39 40
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.util.InitializationException;
41
import org.jivesoftware.util.JiveConstants;
42
import org.jivesoftware.util.JiveGlobals;
43 44
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
45 46 47

/**
 * Creates Cache objects. The returned caches will either be local or clustered
48 49 50
 * depending on the clustering enabled setting and a user's license.
 *
 * <p>When clustered caching is turned on, cache usage statistics for all caches
51
 * that have been created are periodically published to the clustered cache
52
 * named "opt-$cacheStats".</p>
53 54
 *
 */
55
@SuppressWarnings("rawtypes")
56 57
public class CacheFactory {

58
	private static final Logger log = LoggerFactory.getLogger(CacheFactory.class);
59

60 61 62
    public static String LOCAL_CACHE_PROPERTY_NAME = "cache.clustering.local.class";
    public static String CLUSTERED_CACHE_PROPERTY_NAME = "cache.clustering.clustered.class";

63
    private static boolean clusteringStarted = false;
Gaston Dombiak's avatar
Gaston Dombiak committed
64
    private static boolean clusteringStarting = false;
65 66 67 68

    /**
     * Storage for all caches that get created.
     */
69
	private static Map<String, Cache> caches = new ConcurrentHashMap<>();
70
	private static List<String> localOnly = Collections.synchronizedList(new ArrayList<String>());
71
    
72 73
    private static String localCacheFactoryClass;
    private static String clusteredCacheFactoryClass;
74
    private static CacheFactoryStrategy cacheFactoryStrategy = new DefaultLocalCacheStrategy();
75
    private static CacheFactoryStrategy localCacheFactoryStrategy;
76
    private static CacheFactoryStrategy clusteredCacheFactoryStrategy;
77
    private static Thread statsThread;
78

79 80 81 82 83 84 85
    public static final int DEFAULT_MAX_CACHE_SIZE = 1024 * 256;
    public static final long DEFAULT_MAX_CACHE_LIFETIME = 6 * JiveConstants.HOUR;

    /**
     * This map contains property names which were used to store cache configuration data
     * in local xml properties in previous versions.
     */
86
    private static final Map<String, String> cacheNames = new HashMap<>();
87 88 89 90
    /**
     * Default properties to use for local caches. Default properties can be overridden
     * by setting the corresponding system properties.
     */
91
    private static final Map<String, Long> cacheProps = new HashMap<>();
92

93 94
    static {
        localCacheFactoryClass = JiveGlobals.getProperty(LOCAL_CACHE_PROPERTY_NAME,
95
                "org.jivesoftware.util.cache.DefaultLocalCacheStrategy");
96
        clusteredCacheFactoryClass = JiveGlobals.getProperty(CLUSTERED_CACHE_PROPERTY_NAME,
97
                "org.jivesoftware.openfire.plugin.util.cache.ClusteredCacheFactory");
98 99 100 101 102 103 104 105 106 107 108 109 110 111

        cacheNames.put("Favicon Hits", "faviconHits");
        cacheNames.put("Favicon Misses", "faviconMisses");
        cacheNames.put("Group", "group");
        cacheNames.put("Group Metadata Cache", "groupMeta");
        cacheNames.put("Javascript Cache", "javascript");
        cacheNames.put("Last Activity Cache", "lastActivity");
        cacheNames.put("Multicast Service", "multicast");
        cacheNames.put("Offline Message Size", "offlinemessage");
        cacheNames.put("Offline Presence Cache", "offlinePresence");
        cacheNames.put("Privacy Lists", "listsCache");
        cacheNames.put("Remote Users Existence", "remoteUsersCache");
        cacheNames.put("Roster", "username2roster");
        cacheNames.put("User", "userCache");
112
        cacheNames.put("Locked Out Accounts", "lockOutCache");
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
        cacheNames.put("VCard", "vcardCache");
        cacheNames.put("File Transfer Cache", "fileTransfer");
        cacheNames.put("File Transfer", "transferProxy");
        cacheNames.put("POP3 Authentication", "pop3");
        cacheNames.put("LDAP Authentication", "ldap");
        cacheNames.put("Routing Servers Cache", "routeServer");
        cacheNames.put("Routing Components Cache", "routeComponent");
        cacheNames.put("Routing Users Cache", "routeUser");
        cacheNames.put("Routing AnonymousUsers Cache", "routeAnonymousUser");
        cacheNames.put("Routing User Sessions", "routeUserSessions");
        cacheNames.put("Components Sessions", "componentsSessions");
        cacheNames.put("Connection Managers Sessions", "connManagerSessions");
        cacheNames.put("Incoming Server Sessions", "incServerSessions");
        cacheNames.put("Sessions by Hostname", "sessionsHostname");
        cacheNames.put("Secret Keys Cache", "secretKeys");
        cacheNames.put("Validated Domains", "validatedDomains");
        cacheNames.put("Directed Presences", "directedPresences");
        cacheNames.put("Disco Server Features", "serverFeatures");
        cacheNames.put("Disco Server Items", "serverItems");
        cacheNames.put("Remote Server Configurations", "serversConfigurations");
        cacheNames.put("Entity Capabilities", "entityCapabilities");
        cacheNames.put("Entity Capabilities Users", "entityCapabilitiesUsers");
135
        cacheNames.put("PEPServiceManager", "pepServiceManager");
136
        cacheNames.put("Published Items", "publishedItems");
137 138 139 140 141 142 143 144 145 146 147 148 149

        cacheProps.put("cache.fileTransfer.size", 128 * 1024l);
        cacheProps.put("cache.fileTransfer.maxLifetime", 1000 * 60 * 10l);
        cacheProps.put("cache.multicast.size", 128 * 1024l);
        cacheProps.put("cache.multicast.maxLifetime", JiveConstants.DAY);
        cacheProps.put("cache.offlinemessage.size", 100 * 1024l);
        cacheProps.put("cache.offlinemessage.maxLifetime", JiveConstants.HOUR * 12);
        cacheProps.put("cache.pop3.size", 512 * 1024l);
        cacheProps.put("cache.pop3.maxLifetime", JiveConstants.HOUR);
        cacheProps.put("cache.transferProxy.size", -1l);
        cacheProps.put("cache.transferProxy.maxLifetime", 1000 * 60 * 10l);
        cacheProps.put("cache.group.size", 1024 * 1024l);
        cacheProps.put("cache.group.maxLifetime", JiveConstants.MINUTE * 15);
150 151
        cacheProps.put("cache.lockOutCache.size", 1024 * 1024l);
        cacheProps.put("cache.lockOutCache.maxLifetime", JiveConstants.MINUTE * 15);
152 153
        cacheProps.put("cache.groupMeta.size", 512 * 1024l);
        cacheProps.put("cache.groupMeta.maxLifetime", JiveConstants.MINUTE * 15);
154 155
        cacheProps.put("cache.username2roster.size", 1024 * 1024l);
        cacheProps.put("cache.username2roster.maxLifetime", JiveConstants.MINUTE * 30);
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
        cacheProps.put("cache.javascript.size", 128 * 1024l);
        cacheProps.put("cache.javascript.maxLifetime", 3600 * 24 * 10l);
        cacheProps.put("cache.ldap.size", 512 * 1024l);
        cacheProps.put("cache.ldap.maxLifetime", JiveConstants.HOUR * 2);
        cacheProps.put("cache.listsCache.size", 512 * 1024l);
        cacheProps.put("cache.offlinePresence.size", 512 * 1024l);
        cacheProps.put("cache.lastActivity.size", 128 * 1024l);
        cacheProps.put("cache.userCache.size", 512 * 1024l);
        cacheProps.put("cache.userCache.maxLifetime", JiveConstants.MINUTE * 30);
        cacheProps.put("cache.remoteUsersCache.size", 512 * 1024l);
        cacheProps.put("cache.remoteUsersCache.maxLifetime", JiveConstants.MINUTE * 30);
        cacheProps.put("cache.vcardCache.size", 512 * 1024l);
        cacheProps.put("cache.faviconHits.size", 128 * 1024l);
        cacheProps.put("cache.faviconMisses.size", 128 * 1024l);
        cacheProps.put("cache.routeServer.size", -1l);
        cacheProps.put("cache.routeServer.maxLifetime", -1l);
        cacheProps.put("cache.routeComponent.size", -1l);
        cacheProps.put("cache.routeComponent.maxLifetime", -1l);
        cacheProps.put("cache.routeUser.size", -1l);
        cacheProps.put("cache.routeUser.maxLifetime", -1l);
        cacheProps.put("cache.routeAnonymousUser.size", -1l);
        cacheProps.put("cache.routeAnonymousUser.maxLifetime", -1l);
        cacheProps.put("cache.routeUserSessions.size", -1l);
        cacheProps.put("cache.routeUserSessions.maxLifetime", -1l);
        cacheProps.put("cache.componentsSessions.size", -1l);
        cacheProps.put("cache.componentsSessions.maxLifetime", -1l);
        cacheProps.put("cache.connManagerSessions.size", -1l);
        cacheProps.put("cache.connManagerSessions.maxLifetime", -1l);
        cacheProps.put("cache.incServerSessions.size", -1l);
        cacheProps.put("cache.incServerSessions.maxLifetime", -1l);
        cacheProps.put("cache.sessionsHostname.size", -1l);
        cacheProps.put("cache.sessionsHostname.maxLifetime", -1l);
        cacheProps.put("cache.secretKeys.size", -1l);
        cacheProps.put("cache.secretKeys.maxLifetime", -1l);
        cacheProps.put("cache.validatedDomains.size", -1l);
        cacheProps.put("cache.validatedDomains.maxLifetime", -1l);
        cacheProps.put("cache.directedPresences.size", -1l);
        cacheProps.put("cache.directedPresences.maxLifetime", -1l);
        cacheProps.put("cache.serverFeatures.size", -1l);
        cacheProps.put("cache.serverFeatures.maxLifetime", -1l);
        cacheProps.put("cache.serverItems.size", -1l);
        cacheProps.put("cache.serverItems.maxLifetime", -1l);
        cacheProps.put("cache.serversConfigurations.size", 128 * 1024l);
        cacheProps.put("cache.serversConfigurations.maxLifetime", JiveConstants.MINUTE * 30);
        cacheProps.put("cache.entityCapabilities.size", -1l);
        cacheProps.put("cache.entityCapabilities.maxLifetime", JiveConstants.DAY * 2);
        cacheProps.put("cache.entityCapabilitiesUsers.size", -1l);
        cacheProps.put("cache.entityCapabilitiesUsers.maxLifetime", JiveConstants.DAY * 2);
        cacheProps.put("cache.pluginCacheInfo.size", -1l);
        cacheProps.put("cache.pluginCacheInfo.maxLifetime", -1l);
206 207
        cacheProps.put("cache.pepServiceManager.size", 1024l * 1024 * 10);
        cacheProps.put("cache.pepServiceManager.maxLifetime", JiveConstants.MINUTE * 30);
208 209
        cacheProps.put("cache.publishedItems.size", 1024l * 1024 * 10);
        cacheProps.put("cache.publishedItems.maxLifetime", JiveConstants.MINUTE * 15);
210 211 212 213 214
    }

    private CacheFactory() {
    }

215 216 217 218 219 220 221 222 223 224 225 226
    /**
     * If a local property is found for the supplied name which specifies a value for cache size, it is returned.
     * Otherwise, the defaultSize argument is returned.
     *
     * @param cacheName the name of the cache to look up a corresponding property for.
     * @return either the property value or the default value.
     */
    public static long getMaxCacheSize(String cacheName) {
        return getCacheProperty(cacheName, ".size", DEFAULT_MAX_CACHE_SIZE);
    }

    /**
227
     * Sets a local property which overrides the maximum cache size for the
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
     * supplied cache name.
     * @param cacheName the name of the cache to store a value for.
     * @param size the maximum cache size.
     */
    public static void setMaxSizeProperty(String cacheName, long size) {
        cacheName = cacheName.replaceAll(" ", "");
        JiveGlobals.setProperty("cache." + cacheName + ".size", Long.toString(size));
    }

    public static boolean hasMaxSizeFromProperty(String cacheName) {
        return hasCacheProperty(cacheName, ".size");
    }

    /**
    * If a local property is found for the supplied name which specifies a value for cache entry lifetime, it
     * is returned. Otherwise, the defaultLifetime argument is returned.
     *
    * @param cacheName the name of the cache to look up a corresponding property for.
    * @return either the property value or the default value.
    */
    public static long getMaxCacheLifetime(String cacheName) {
        return getCacheProperty(cacheName, ".maxLifetime", DEFAULT_MAX_CACHE_LIFETIME);
    }

    /**
253
     * Sets a local property which overrides the maximum cache entry lifetime
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
     * for the supplied cache name.
     * @param cacheName the name of the cache to store a value for.
     * @param lifetime the maximum cache entry lifetime.
     */
    public static void setMaxLifetimeProperty(String cacheName, long lifetime) {
        cacheName = cacheName.replaceAll(" ", "");
        JiveGlobals.setProperty(("cache." + cacheName + ".maxLifetime"), Long.toString(lifetime));
    }

    public static boolean hasMaxLifetimeFromProperty(String cacheName) {
        return hasCacheProperty(cacheName, ".maxLifetime");
    }

    public static void setCacheTypeProperty(String cacheName, String type) {
        cacheName = cacheName.replaceAll(" ", "");
        JiveGlobals.setProperty("cache." + cacheName + ".type", type);
    }

    public static String getCacheTypeProperty(String cacheName) {
        cacheName = cacheName.replaceAll(" ", "");
        return JiveGlobals.getProperty("cache." + cacheName + ".type");
    }

    public static void setMinCacheSize(String cacheName, long size) {
        cacheName = cacheName.replaceAll(" ", "");
        JiveGlobals.setProperty("cache." + cacheName + ".min", Long.toString(size));
    }

    public static long getMinCacheSize(String cacheName) {
        return getCacheProperty(cacheName, ".min", 0);
    }

    private static long getCacheProperty(String cacheName, String suffix, long defaultValue) {
        // First check if user is overwriting default value using a system property for the cache name
        String propName = "cache." + cacheName.replaceAll(" ", "") + suffix;
        String sizeProp = JiveGlobals.getProperty(propName);
        if (sizeProp == null && cacheNames.containsKey(cacheName)) {
            // No system property was found for the cache name so try now with short name
            propName = "cache." + cacheNames.get(cacheName) + suffix;
            sizeProp = JiveGlobals.getProperty(propName);
        }
        if (sizeProp != null) {
            try {
                return Long.parseLong(sizeProp);
            }
            catch (NumberFormatException nfe) {
300
                log.warn("Unable to parse " + propName + " using default value.");
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
            }
        }
        // Check if there is a default size value for this cache
        Long defaultSize = cacheProps.get(propName);
        return defaultSize == null ? defaultValue : defaultSize;
    }

    private static boolean hasCacheProperty(String cacheName, String suffix) {
        // First check if user is overwriting default value using a system property for the cache name
        String propName = "cache." + cacheName.replaceAll(" ", "") + suffix;
        String sizeProp = JiveGlobals.getProperty(propName);
        if (sizeProp == null && cacheNames.containsKey(cacheName)) {
            // No system property was found for the cache name so try now with short name
            propName = "cache." + cacheNames.get(cacheName) + suffix;
            sizeProp = JiveGlobals.getProperty(propName);
        }
        if (sizeProp != null) {
            try {
                Long.parseLong(sizeProp);
                return true;
            }
            catch (NumberFormatException nfe) {
323
                log.warn("Unable to parse " + propName + " using default value.");
324 325 326 327 328
            }
        }
        return false;
    }

329 330 331 332
    /**
     * Returns an array of all caches in the system.
     * @return an array of all caches in the system.
     */
333
    public static Cache[] getAllCaches() {
334
        List<Cache> values = new ArrayList<>();
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
        for (Cache cache : caches.values()) {
            values.add(cache);
        }
        return values.toArray(new Cache[values.size()]);
    }

    /**
     * Returns the named cache, creating it as necessary.
     *
     * @param name         the name of the cache to create.
     * @return the named cache, creating it as necessary.
     */
    @SuppressWarnings("unchecked")
    public static synchronized <T extends Cache> T createCache(String name) {
        T cache = (T) caches.get(name);
        if (cache != null) {
            return cache;
        }
        cache = (T) cacheFactoryStrategy.createCache(name);
354
        
355
        log.info("Created cache [" + cacheFactoryStrategy.getClass().getName() + "] for " + name);
356 357 358 359

        return wrapCache(cache, name);
    }

360 361 362 363 364 365 366 367 368 369 370 371 372
    /**
     * Returns the named local cache, creating it as necessary.
     *
     * @param name         the name of the cache to create.
     * @return the named cache, creating it as necessary.
     */
    @SuppressWarnings("unchecked")
    public static synchronized <T extends Cache> T createLocalCache(String name) {
        T cache = (T) caches.get(name);
        if (cache != null) {
            return cache;
        }
        cache = (T) localCacheFactoryStrategy.createCache(name);
373
        localOnly.add(name);
374

375
        log.info("Created local-only cache [" + localCacheFactoryClass + "] for " + name);
376
        
377 378 379
        return wrapCache(cache, name);
    }

380 381 382 383 384
    /**
     * Destroys the cache for the cache name specified.
     *
     * @param name the name of the cache to destroy.
     */
385
    public static synchronized void destroyCache(String name) {
386 387
        Cache cache = caches.remove(name);
        if (cache != null) {
388 389 390 391 392 393
            if (localOnly.contains(name)) {
            	localOnly.remove(name);
            	localCacheFactoryStrategy.destroyCache(cache);
            } else {
            	cacheFactoryStrategy.destroyCache(cache);
            }
394 395 396
        }
    }

397 398 399 400 401 402 403 404 405 406 407 408 409 410
    /**
     * Returns an existing {@link java.util.concurrent.locks.Lock} on the specified key or creates a new one
     * if none was found. This operation is thread safe. Successive calls with the same key may or may not
     * return the same {@link java.util.concurrent.locks.Lock}. However, different threads asking for the
     * same Lock at the same time will get the same Lock object.<p>
     *
     * The supplied cache may or may not be used depending whether the server is running on cluster mode
     * or not. When not running as part of a cluster then the lock will be unrelated to the cache and will
     * only be visible in this JVM.
     *
     * @param key the object that defines the visibility or scope of the lock.
     * @param cache the cache used for holding the lock.
     * @return an existing lock on the specified key or creates a new one if none was found.
     */
411
    public static synchronized Lock getLock(Object key, Cache cache) {
412 413 414
        if (localOnly.contains(cache.getName())) {
        	return localCacheFactoryStrategy.getLock(key, cache);
        } else {
415
        	return cacheFactoryStrategy.getLock(key, cache);
416
        }
417 418 419 420
    }

    @SuppressWarnings("unchecked")
    private static <T extends Cache> T wrapCache(T cache, String name) {
421 422 423 424 425
    	if ("Routing Components Cache".equals(name)) {
            cache = (T) new ComponentCacheWrapper(cache);
    	} else {
            cache = (T) new CacheWrapper(cache);
    	}
426 427 428 429 430 431
        cache.setName(name);

        caches.put(name, cache);
        return cache;
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
432
    /**
Gaston Dombiak's avatar
Gaston Dombiak committed
433 434 435 436
     * Returns true if clustering is installed and can be used by this JVM
     * to join a cluster. A false value could mean that either clustering
     * support is not available or the license does not allow to have more
     * than 1 cluster node.
Gaston Dombiak's avatar
Gaston Dombiak committed
437 438 439 440 441
     *
     * @return true if clustering is installed and can be used by
     * this JVM to join a cluster.
     */
    public static boolean isClusteringAvailable() {
442 443 444 445 446
    	if (clusteredCacheFactoryStrategy == null) {
	        try {
	        	clusteredCacheFactoryStrategy = (CacheFactoryStrategy) Class.forName(
	        			clusteredCacheFactoryClass, true,
	        			getClusteredCacheStrategyClassLoader()).newInstance();
447
	        } catch (NoClassDefFoundError | Exception e) {
448 449
	        	log.warn("Clustered cache factory strategy " + clusteredCacheFactoryClass + " not found");
	        }
450
        }
451
    	return (clusteredCacheFactoryStrategy != null);
Gaston Dombiak's avatar
Gaston Dombiak committed
452 453 454 455 456 457 458 459 460 461 462 463
    }

    /**
     * Returns true is clustering is currently being started. Once the cluster
     * is started or failed to be started this value will be false.
     *
     * @return true is clustering is currently being started.
     */
    public static boolean isClusteringStarting() {
        return clusteringStarting;
    }

464 465 466 467 468 469
    /**
     * Returns true if this node is currently a member of a cluster. The last step of application
     * initialization is to join a cluster, so this method returns false during most of application startup.
     *
     * @return true if this node is currently a member of a cluster.
     */
470 471
    public static boolean isClusteringStarted() {
        return clusteringStarted;
472 473 474
    }

    /**
475 476
     * Returns a byte[] that uniquely identifies this member within the cluster or <tt>null</tt>
     * when not in a cluster.
477
     *
478
     * @return a byte[] that uniquely identifies this member within the cluster or null when not in a cluster.
479
     */
480
    public static byte[] getClusterMemberID() {
481 482 483 484 485 486 487 488 489 490
        return cacheFactoryStrategy.getClusterMemberID();
    }

    public synchronized static void clearCaches() {
        for (String cacheName : caches.keySet()) {
            Cache cache = caches.get(cacheName);
            cache.clear();
        }
    }

491 492 493 494 495 496 497 498 499 500
    /**
     * Returns a byte[] that uniquely identifies this senior cluster member or <tt>null</tt>
     * when not in a cluster.
     *
     * @return a byte[] that uniquely identifies this senior cluster member or null when not in a cluster.
     */
    public static byte[] getSeniorClusterMemberID() {
        return cacheFactoryStrategy.getSeniorClusterMemberID();
    }

501 502 503 504 505 506 507 508 509 510 511
    /**
     * Returns true if this member is the senior member in the cluster. If clustering
     * is not enabled, this method will also return true. This test is useful for
     * tasks that should only be run on a single member in a cluster.
     *
     * @return true if this cluster member is the senior or if clustering is not enabled.
     */
    public static boolean isSeniorClusterMember() {
        return cacheFactoryStrategy.isSeniorClusterMember();
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
512 513 514 515 516 517 518 519 520 521 522
    /**
     * Returns basic information about the current members of the cluster or an empty
     * collection if not running in a cluster.
     *
     * @return information about the current members of the cluster or an empty
     *         collection if not running in a cluster.
     */
    public static Collection<ClusterNodeInfo> getClusterNodesInfo() {
        return cacheFactoryStrategy.getClusterNodesInfo();
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
523
    /**
524
     * Returns the maximum number of cluster members allowed. A value of 0 will
Gaston Dombiak's avatar
Gaston Dombiak committed
525 526
     * be returned when clustering is not allowed.
     *
527
     * @return the maximum number of cluster members allowed or 0 if clustering is not allowed.
Gaston Dombiak's avatar
Gaston Dombiak committed
528 529
     */
    public static int getMaxClusterNodes() {
530
    	return cacheFactoryStrategy.getMaxClusterNodes();
Gaston Dombiak's avatar
Gaston Dombiak committed
531
    }
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
    
    /**
     * Gets the pseudo-synchronized time from the cluster. While the cluster members may
     * have varying system times, this method is expected to return a timestamp that is
     * synchronized (or nearly so; best effort) across the cluster.
     * 
     * @return Synchronized time for all cluster members
     */
    public static long getClusterTime() {
    	// use try/catch here for backward compatibility with older plugin(s)
    	try { return cacheFactoryStrategy.getClusterTime(); }
    	catch (AbstractMethodError ame) {
    		log.warn("Cluster time not available; check for update to hazelcast/clustering plugin");
    		return localCacheFactoryStrategy.getClusterTime();
    	}
    }
    
549 550 551 552 553 554 555
    /**
     * Invokes a task on other cluster members in an asynchronous fashion. The task will not be
     * executed on the local cluster member. If clustering is not enabled, this method
     * will do nothing.
     *
     * @param task the task to be invoked on all other cluster members.
     */
556
    public static void doClusterTask(final ClusterTask<?> task) {
557
        cacheFactoryStrategy.doClusterTask(task);
558 559
    }

560 561 562 563 564 565
    /**
     * Invokes a task on a given cluster member in an asynchronous fashion. If clustering is not enabled,
     * this method will do nothing.
     *
     * @param task the task to be invoked on the specified cluster member.
     * @param nodeID the byte array that identifies the target cluster member.
566
     * @throws IllegalStateException if requested node was not found or not running in a cluster. 
567
     */
568
    public static void doClusterTask(final ClusterTask<?> task, byte[] nodeID) {
569
        cacheFactoryStrategy.doClusterTask(task, nodeID);
570 571
    }

572 573 574 575 576 577 578 579 580 581
    /**
     * Invokes a task on other cluster members synchronously and returns the result as a Collection
     * (method will not return until the task has been executed on each cluster member).
     * The task will not be executed on the local cluster member. If clustering is not enabled,
     * this method will return an empty collection.
     *
     * @param task               the ClusterTask object to be invoked on all other cluster members.
     * @param includeLocalMember true to run the task on the local member, false otherwise
     * @return collection with the result of the execution.
     */
582
    public static Collection<Object> doSynchronousClusterTask(ClusterTask<?> task, boolean includeLocalMember) {
583
        return cacheFactoryStrategy.doSynchronousClusterTask(task, includeLocalMember);
584 585
    }

586 587 588 589 590 591 592
    /**
     * Invokes a task on a given cluster member synchronously and returns the result of
     * the remote operation. If clustering is not enabled, this method will return null.
     *
     * @param task        the ClusterTask object to be invoked on a given cluster member.
     * @param nodeID      the byte array that identifies the target cluster member.
     * @return result of remote operation or null if operation failed or operation returned null.
593
     * @throws IllegalStateException if requested node was not found or not running in a cluster.
594
     */
595
    public static Object doSynchronousClusterTask(ClusterTask<?> task, byte[] nodeID) {
596 597
        return cacheFactoryStrategy.doSynchronousClusterTask(task, nodeID);
    }
598 599 600 601 602 603 604 605 606
    
    /**
     * Returns the node info for the given cluster node
     * @param nodeID The target cluster node 
     * @return The info for the cluster node or null if not found
     */
    public static ClusterNodeInfo getClusterNodeInfo(byte[] nodeID) {
    	return cacheFactoryStrategy.getClusterNodeInfo(nodeID);
    }
607

608 609 610 611
    public static String getPluginName() {
        return cacheFactoryStrategy.getPluginName();
    }

612 613
    public static synchronized void initialize() throws InitializationException {
        try {
614 615 616 617 618
        	localCacheFactoryStrategy = (CacheFactoryStrategy) Class.forName(localCacheFactoryClass).newInstance();
            cacheFactoryStrategy = localCacheFactoryStrategy;
        } catch (Exception e) {
        	log.error("Failed to instantiate local cache factory strategy: " + localCacheFactoryClass, e);
        	 throw new InitializationException(e);
619 620 621
        }
    }

622
    private static ClassLoader getClusteredCacheStrategyClassLoader() {
623
        PluginManager pluginManager = XMPPServer.getInstance().getPluginManager();
624
        Plugin plugin = pluginManager.getPlugin("hazelcast");
625
        if (plugin == null) {
626 627 628 629
            plugin = pluginManager.getPlugin("clustering");
            if (plugin == null) {
                plugin = pluginManager.getPlugin("enterprise");
            }
630 631
        }
        PluginClassLoader pluginLoader = pluginManager.getPluginClassloader(plugin);
632
        if (pluginLoader != null) {
633 634 635 636 637 638 639 640
        	if (log.isDebugEnabled()) {
        		StringBuffer pluginLoaderDetails = new StringBuffer("Clustering plugin class loader: ");
        		pluginLoaderDetails.append(pluginLoader.getClass().getName());
        		for (URL url : pluginLoader.getURLs()) {
        			pluginLoaderDetails.append("\n\t").append(url.toExternalForm());
        		}
        		log.debug(pluginLoaderDetails.toString());
        	}
641
            return pluginLoader;
642 643
        }
        else {
644
            log.warn("CacheFactory - Unable to find a Plugin that provides clustering support.");
645 646 647 648
            return Thread.currentThread().getContextClassLoader();
        }
    }

649
    public static void startClustering() {
650 651 652
    	if (isClusteringAvailable()) {
    		clusteringStarting = clusteredCacheFactoryStrategy.startCluster();
    	}
653
        if (clusteringStarting) {
654 655 656 657 658
            if (statsThread == null) {
                // Start a timing thread with 1 second of accuracy.
                statsThread = new Thread("Cache Stats") {
                    private volatile boolean destroyed = false;

659 660
                    @Override
					public void run() {
661
                        XMPPServer.getInstance().addServerListener(new XMPPServerListener() {
662
                            @Override
663 664
                            public void serverStarted() {}

665
                            @Override
666 667 668 669
                            public void serverStopping() {
                                destroyed = true;
                            }
                        });
Gaston Dombiak's avatar
Gaston Dombiak committed
670
                        ClusterManager.addListener(new ClusterEventListener() {
671
                            @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
672 673
                            public void joinedCluster() {}

674
                            @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
675 676
                            public void joinedCluster(byte[] nodeID) {}

677
                            @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
678 679 680 681 682
                            public void leftCluster() {
                                destroyed = true;
                                ClusterManager.removeListener(this);
                            }

683
                            @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
684 685
                            public void leftCluster(byte[] nodeID) {}

686
                            @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
687 688
                            public void markedAsSeniorClusterMember() {}
                        });
689 690

                        // Run the timer indefinitely.
Gaston Dombiak's avatar
Gaston Dombiak committed
691
                        while (!destroyed && ClusterManager.isClusteringEnabled()) {
692 693 694 695 696 697
                            // Publish cache stats for this cluster node (assuming clustering is
                            // enabled and there are stats to publish).
                            try {
                                cacheFactoryStrategy.updateCacheStats(caches);
                            }
                            catch (Exception e) {
698
                                log.error(e.getMessage(), e);
699 700 701 702 703 704 705 706 707
                            }
                            try {
                                // Sleep 10 seconds.
                                sleep(10000);
                            }
                            catch (InterruptedException ie) {
                                // Ignore.
                            }
                        }
Gaston Dombiak's avatar
Gaston Dombiak committed
708
                        statsThread = null;
709
                        log.debug("Cache stats thread terminated.");
710 711 712 713 714 715
                    }
                };
                statsThread.setDaemon(true);
                statsThread.start();
            }
        }
716 717
    }

718
    public static void stopClustering() {
719
        // Stop the cluster
720
    	clusteredCacheFactoryStrategy.stopCluster();
721
    	clusteredCacheFactoryStrategy = null;
722 723
        // Set the strategy to local
        cacheFactoryStrategy = localCacheFactoryStrategy;
724 725
    }

726
    /**
727
     * Notification message indicating that this JVM has joined a cluster.
728
     */
729
    @SuppressWarnings("unchecked")
730 731
	public static synchronized void joinedCluster() {
        cacheFactoryStrategy = clusteredCacheFactoryStrategy;
732
        // Loop through local caches and switch them to clustered cache (copy content)
733
        for (Cache cache : getAllCaches()) {
734 735
            // skip local-only caches
            if (localOnly.contains(cache.getName())) continue;
736 737
            CacheWrapper cacheWrapper = ((CacheWrapper) cache);
            Cache clusteredCache = cacheFactoryStrategy.createCache(cacheWrapper.getName());
738
            clusteredCache.putAll(cache);
739
            cacheWrapper.setWrappedCache(clusteredCache);
740
        }
741 742
        clusteringStarting = false;
        clusteringStarted = true;
743
        log.info("Clustering started; cache migration complete");
744 745
    }

746 747 748
    /**
     * Notification message indicating that this JVM has left the cluster.
     */
749
    @SuppressWarnings("unchecked")
750
	public static synchronized void leftCluster() {
751
        clusteringStarted = false;
752
        cacheFactoryStrategy = localCacheFactoryStrategy;
Gaston Dombiak's avatar
Gaston Dombiak committed
753

754
        // Loop through clustered caches and change them to local caches (copy content)
755 756 757 758 759
        for (Cache cache : getAllCaches()) {
            // skip local-only caches
            if (localOnly.contains(cache.getName())) continue;
            CacheWrapper cacheWrapper = ((CacheWrapper) cache);
            Cache standaloneCache = cacheFactoryStrategy.createCache(cacheWrapper.getName());
760
            standaloneCache.putAll(cache);
761 762 763
            cacheWrapper.setWrappedCache(standaloneCache);
    	}
        log.info("Clustering stopped; cache migration complete");
764
    }
765
}