/* * Copyright (C) 2011 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.services.jcr.impl.core.query.ispn; import org.exoplatform.services.jcr.impl.core.query.ChangesFilterListsWrapper; import org.exoplatform.services.jcr.impl.core.query.IndexerIoMode; import org.exoplatform.services.jcr.impl.core.query.IndexerIoModeHandler; import org.exoplatform.services.jcr.util.IdGenerator; import org.infinispan.Cache; import org.infinispan.container.DataContainer; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.lifecycle.ComponentStatus; import org.infinispan.loaders.CacheLoaderConfig; import org.infinispan.loaders.CacheLoaderException; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.marshall.StreamingMarshaller; import org.infinispan.notifications.Listener; import org.infinispan.notifications.cachemanagerlistener.annotation.CacheStarted; import org.infinispan.notifications.cachemanagerlistener.annotation.Merged; import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged; import org.infinispan.notifications.cachemanagerlistener.event.Event; import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent; import org.infinispan.remoting.transport.Address; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Implements Cache Store for clustered environment. It gives control of Index for coordinator and * adds failover mechanisms when it changes. * * @author <a href="mailto:nikolazius@gmail.com">Nikolay Zamosenchuk</a> * @version $Id: IndexCacheLoader.java 34360 2009-07-22 23:58:59Z nzamosenchuk $ * */ public class IndexerCacheStore extends AbstractIndexerCacheStore { protected volatile IndexerIoModeHandler modeHandler; protected CacheListener listener; /** * Address instance that allows SingletonStore to find out whether it became the coordinator of the cluster, or * whether it stopped being it. This dictates whether the SingletonStore is active or not. */ private Address localAddress; /** * Whether the the current cache is the coordinator and therefore SingletonStore is active. Being active means * delegating calls to the underlying cache loader. */ private volatile boolean coordinator; protected EmbeddedCacheManager cacheManager; @Override public void init(CacheLoaderConfig config, Cache<?, ?> cache, StreamingMarshaller m) throws CacheLoaderException { super.init(config, cache, m); this.cacheManager = cache == null ? null : cache.getCacheManager(); listener = new CacheListener(); cacheManager.addListener(listener); } /** * Get the mode handler */ public IndexerIoModeHandler getModeHandler() { if (modeHandler == null) { if (cache.getStatus() != ComponentStatus.RUNNING) { throw new IllegalStateException("The cache should be started first"); } synchronized (this) { if (modeHandler == null) { this.modeHandler = new IndexerIoModeHandler(cacheManager.isCoordinator() || cache.getAdvancedCache().getRpcManager() == null ? IndexerIoMode.READ_WRITE : IndexerIoMode.READ_ONLY); } } } return modeHandler; } /** * Indicates whether the current cache is the coordinator of the cluster. This implementation assumes that the * coordinator is the first member in the list. * * @param newView View instance containing the new view of the cluster * @return whether the current cache is the coordinator or not. */ private boolean isCoordinator(List<Address> newView, Address currentAddress) { if (!currentAddress.equals(localAddress)) { localAddress = currentAddress; } if (localAddress != null) { return !newView.isEmpty() && localAddress.equals(newView.get(0)); } else { /* Invalid new view, so previous value returned */ return coordinator; } } /** * Method called when the cache either becomes the coordinator or stops being the coordinator. If it becomes the * coordinator, it can optionally start the in-memory state transfer to the underlying cache store. * * @param newActiveState true if the cache just became the coordinator, false if the cache stopped being the * coordinator. */ protected void activeStatusChanged(final boolean newActiveState) { // originally came from EXOJCR-1345. // Deadlock occurs inside JGroups, if calling some operations inside the same thread, // invoking ViewChanged. That's why, need to perform operation in separated async tread. new Thread(new Runnable() { public void run() { coordinator = newActiveState; getModeHandler().setMode(coordinator ? IndexerIoMode.READ_WRITE : IndexerIoMode.READ_ONLY); LOG.info("Set indexer io mode to:" + (coordinator ? IndexerIoMode.READ_WRITE : IndexerIoMode.READ_ONLY)); if (coordinator) { doPushState(); } } }, "JCR Indexer ActiveStatusChanged-handler").start(); } /** * Flushes all cache content to underlying CacheStore */ @SuppressWarnings("rawtypes") protected void doPushState() { final boolean debugEnabled = LOG.isDebugEnabled(); if (debugEnabled) { LOG.debug("start pushing in-memory state to cache cacheLoader collection"); } Map<String, ChangesFilterListsWrapper> changesMap = new HashMap<String, ChangesFilterListsWrapper>(); List<ChangesKey> processedItemKeys = new ArrayList<ChangesKey>(); DataContainer dc = cache.getAdvancedCache().getDataContainer(); Set keys = dc.keySet(); InternalCacheEntry entry; // collect all cache entries into the following map: // <WS ID> : <Contracted lists of added/removed nodes> for (Object k : keys) { if ((entry = dc.get(k)) != null) { if (entry.getValue() instanceof ChangesFilterListsWrapper && entry.getKey() instanceof ChangesKey) { if (debugEnabled) { LOG.debug("Received list wrapper, start indexing..."); } // get stale List that was not processed ChangesFilterListsWrapper staleListIncache = (ChangesFilterListsWrapper)entry.getValue(); ChangesKey key = (ChangesKey)entry.getKey(); // get newly created wrapper instance ChangesFilterListsWrapper listToPush = changesMap.get(key.getWsId()); if (listToPush == null) { listToPush = new ChangesFilterListsWrapper(new HashSet<String>(), new HashSet<String>(), new HashSet<String>(), new HashSet<String>()); changesMap.put(key.getWsId(), listToPush); } // copying lists into the new wrapper if (staleListIncache.getParentAddedNodes() != null) listToPush.getParentAddedNodes().addAll(staleListIncache.getParentAddedNodes()); if (staleListIncache.getParentRemovedNodes() != null) listToPush.getParentRemovedNodes().addAll(staleListIncache.getParentRemovedNodes()); if (staleListIncache.getAddedNodes() != null) listToPush.getAddedNodes().addAll(staleListIncache.getAddedNodes()); if (staleListIncache.getRemovedNodes() != null) listToPush.getRemovedNodes().addAll(staleListIncache.getRemovedNodes()); processedItemKeys.add(key); } } } // process all lists for each workspace for (Entry<String, ChangesFilterListsWrapper> changesEntry : changesMap.entrySet()) { // create key based on wsId and generated id ChangesKey changesKey = new ChangesKey(changesEntry.getKey(), IdGenerator.generate()); cache.putAsync(changesKey, changesEntry.getValue()); } for (ChangesKey key : processedItemKeys) { cache.removeAsync(key); } if (debugEnabled) { LOG.debug("in-memory state passed to cache cacheStore successfully"); } } @Listener public class CacheListener { @CacheStarted public void cacheStarted(Event e) { localAddress = cacheManager.getAddress(); coordinator = cacheManager.isCoordinator(); } /** * The cluster formation changed, so determine whether the current cache stopped being the coordinator or became * the coordinator. This method can lead to an optional in memory to cache loader state push, if the current cache * became the coordinator. This method will report any issues that could potentially arise from this push. */ @ViewChanged @Merged public void viewChange(ViewChangedEvent event) { LOG.info("The intercepted EventType is : " + event.getType()); LOG.info("The old list of members : " + event.getOldMembers()); LOG.info("The new list of members: : " + event.getNewMembers()); boolean tmp = isCoordinator(event.getNewMembers(), event.getLocalAddress()); if (coordinator != tmp || (tmp && event.isMergeView())) { activeStatusChanged(tmp); } } } /** * {@inheritDoc} */ @Override public void stop() throws CacheLoaderException { cacheManager.removeListener(listener); super.stop(); } }