/*
* JBoss, Home of Professional Open Source
* Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* 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,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.infinispan.cacheviews;
import org.infinispan.remoting.MembershipArithmetic;
import org.infinispan.remoting.transport.Address;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* The cluster-wide state of a cache.
*
* @author Dan Berindei <dan@infinispan.org>
*/
public class CacheViewInfo {
private static final Log log = LogFactory.getLog(CacheViewInfo.class);
private final String cacheName;
private final Object lock = new Object();
private final List<CacheView> viewHistory = new LinkedList<CacheView>();
// The last view for which the coordinator sent a COMMIT_VIEW message
private CacheView committedView;
// The last view for which the coordinator sent a PREPARE_VIEW message, or null if it has already been committed
private CacheView pendingView;
// TODO These two don't really belong to the view state, but we keep them here to avoid creating other map
// The cache-scoped listener
private volatile CacheViewListener listener;
// The view installation task - only used if this node is the coordinator
private final PendingCacheViewChanges pendingChanges;
public CacheViewInfo(String cacheName, CacheView initialView) {
log.tracef("%s: Initializing state, initial view is %s", cacheName, initialView);
this.cacheName = cacheName;
this.committedView = initialView;
this.pendingView = null;
this.pendingChanges = new PendingCacheViewChanges(cacheName);
}
public String getCacheName() {
return cacheName;
}
public CacheView getCommittedView() {
synchronized (lock) {
return committedView;
}
}
public CacheView getPendingView() {
synchronized (lock) {
return pendingView;
}
}
public final List<CacheView> getViewHistory() {
synchronized (lock) {
return new ArrayList<CacheView>(viewHistory);
}
}
/**
* We only support one listener per cache.
* TODO Consider moving the listener to the <tt>CacheNotifier</tt> interface.
* @param listener
*/
public void setListener(CacheViewListener listener) {
this.listener = listener;
}
public CacheViewListener getListener() {
return listener;
}
public PendingCacheViewChanges getPendingChanges() {
return pendingChanges;
}
/**
* Update the pending view.
* It does nothing on the coordinator, since {@code createPendingView} already updated the pending view.
*/
public void prepareView(CacheView newView) {
log.tracef("%s: Preparing view %s", cacheName, newView);
synchronized (lock) {
if (pendingView != null) {
throw new IllegalStateException(String.format("Cannot prepare new view %s on cache %s, we are currently preparing view %s",
newView, cacheName, pendingView));
}
if (committedView.getViewId() > newView.getViewId()) {
throw new IllegalStateException(String.format("Cannot prepare new view %s on cache %s, we have already committed view %s",
newView, cacheName, committedView));
}
this.pendingView = newView;
}
}
/**
* Update the committed view
*/
public void commitView(int viewId) {
log.tracef("%s: Committing view %s", cacheName, viewId);
synchronized (lock) {
// We need to allow re-committing the same view
if (pendingView == null && viewId == committedView.getViewId()) {
log.tracef("%s: Re-committing view %d", cacheName, viewId);
return;
}
if (pendingView == null || viewId != pendingView.getViewId())
throw new IllegalArgumentException(String.format("Cannot commit view %d, we are currently preparing view %s", viewId, pendingView));
committedView = pendingView;
pendingView = null;
viewHistory.add(committedView);
}
}
/**
* Discard the pending view
*/
public void rollbackView(int newViewId, int committedViewId) {
log.tracef("%s: Rolling back to cache view %s, new view id is %d", cacheName, committedView, newViewId);
synchronized (lock) {
// Before we install the first view we don't have a proper view to go to
if (!committedView.getMembers().isEmpty() && committedViewId != committedView.getViewId()) {
log.cacheViewRollbackIdMismatch(committedViewId, committedView.getViewId());
}
pendingView = null;
committedView = new CacheView(newViewId, committedView.getMembers());
}
}
boolean hasPendingView() {
return getPendingView() != null || getPendingChanges().isViewInstallationInProgress();
}
/**
* @return The list of members that are no longer present in the {@code newMembers} list.
* Includes both committed and pending members.
*/
public List<Address> computeLeavers(List<Address> newMembers) {
List<Address> leavers = MembershipArithmetic.getMembersLeft(getCommittedView().getMembers(), newMembers);
leavers.addAll(getPendingChanges().computeMissingJoiners(newMembers));
return leavers;
}
public final void gc(int minViewId) {
synchronized (lock) {
Iterator<CacheView> iterator = viewHistory.iterator();
while (iterator.hasNext()) {
if (iterator.next().getViewId() < minViewId) {
iterator.remove();
}
}
}
}
}