/* * INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa * Copyright 2013 INESC-ID 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 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 3.0 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.infinispan.transaction.gmu; import org.infinispan.CacheException; import org.infinispan.commands.tx.GMUPrepareCommand; import org.infinispan.container.DataContainer; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.container.entries.gmu.InternalGMUCacheEntry; import org.infinispan.container.versioning.EntryVersion; import org.infinispan.container.versioning.InequalVersionComparisonResult; import org.infinispan.container.versioning.VersionGenerator; import org.infinispan.container.versioning.gmu.GMUVersion; import org.infinispan.container.versioning.gmu.GMUVersionGenerator; import org.infinispan.context.impl.TxInvocationContext; import org.infinispan.interceptors.locking.ClusteringDependentLogic; import org.infinispan.remoting.responses.ExceptionResponse; import org.infinispan.remoting.responses.Response; import org.infinispan.remoting.responses.SuccessfulResponse; import org.infinispan.remoting.transport.Address; import org.infinispan.transaction.xa.GlobalTransaction; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import java.util.Collection; import java.util.LinkedList; import java.util.List; /** * // TODO: Document this * * @author Pedro Ruivo * @since 5.2 */ public class GMUHelper { private static final Log log = LogFactory.getLog(GMUHelper.class); public static void performReadSetValidation(GMUPrepareCommand prepareCommand, DataContainer dataContainer, ClusteringDependentLogic keyLogic) { GlobalTransaction gtx = prepareCommand.getGlobalTransaction(); if (prepareCommand.getReadSet() == null || prepareCommand.getReadSet().length == 0) { if (log.isDebugEnabled()) { log.debugf("Validation of [%s] OK. no read set", gtx.prettyPrint()); } return; } EntryVersion prepareVersion = prepareCommand.getPrepareVersion(); for (Object key : prepareCommand.getReadSet()) { if (keyLogic.localNodeIsOwner(key)) { InternalCacheEntry cacheEntry = dataContainer.get(key, null); //get the most recent EntryVersion currentVersion = cacheEntry.getVersion(); if (log.isDebugEnabled()) { log.debugf("[%s] Validate [%s]: Compare %s vs %s", gtx.prettyPrint(), key, currentVersion, prepareVersion); } if (currentVersion == null) { //this should only happens if the key does not exits. However, this can create some //consistency issues when eviction is enabled continue; } if (currentVersion.compareTo(prepareVersion) == InequalVersionComparisonResult.AFTER) { throw new ValidationException("Validation failed for key [" + key + "]", key); } } else { if (log.isDebugEnabled()) { log.debugf("[%s] Validate [%s]: keys is not local", gtx.prettyPrint(), key); } } } } public static EntryVersion calculateCommitVersion(EntryVersion mergedVersion, GMUVersionGenerator versionGenerator, Collection<Address> affectedOwners) { if (mergedVersion == null) { throw new NullPointerException("Null merged version is not allowed to calculate commit view"); } return versionGenerator.calculateCommitVersion(mergedVersion, affectedOwners); } public static InternalGMUCacheEntry toInternalGMUCacheEntry(InternalCacheEntry entry) { return convert(entry, InternalGMUCacheEntry.class); } public static GMUVersion toGMUVersion(EntryVersion version) { return convert(version, GMUVersion.class); } public static GMUVersionGenerator toGMUVersionGenerator(VersionGenerator versionGenerator) { return convert(versionGenerator, GMUVersionGenerator.class); } public static <T> T convert(Object object, Class<T> clazz) { if (log.isDebugEnabled()) { log.debugf("Convert object %s to class %s", object, clazz.getCanonicalName()); } try { return clazz.cast(object); } catch (ClassCastException cce) { log.fatalf(cce, "Error converting object %s to class %s", object, clazz.getCanonicalName()); throw new IllegalArgumentException("Expected " + clazz.getSimpleName() + " and not " + object.getClass().getSimpleName()); } } public static void joinAndSetTransactionVersion(Collection<Response> responses, TxInvocationContext ctx, GMUVersionGenerator versionGenerator) { if (responses.isEmpty()) { if (log.isDebugEnabled()) { log.debugf("Versions received are empty!"); } return; } List<EntryVersion> allPreparedVersions = new LinkedList<EntryVersion>(); allPreparedVersions.add(ctx.getTransactionVersion()); GlobalTransaction gtx = ctx.getGlobalTransaction(); //process all responses for (Response r : responses) { if (r == null) { throw new IllegalStateException("Non-null response with new version is expected"); } else if (r instanceof SuccessfulResponse) { EntryVersion version = convert(((SuccessfulResponse) r).getResponseValue(), EntryVersion.class); allPreparedVersions.add(version); } else if(r instanceof ExceptionResponse) { throw new ValidationException(((ExceptionResponse) r).getException()); } else if(!r.isSuccessful()) { throw new CacheException("Unsuccessful response received... aborting transaction " + gtx.prettyPrint()); } } EntryVersion[] preparedVersionsArray = new EntryVersion[allPreparedVersions.size()]; EntryVersion commitVersion = versionGenerator.mergeAndMax(allPreparedVersions.toArray(preparedVersionsArray)); if (log.isTraceEnabled()) { log.tracef("Merging transaction [%s] prepare versions %s ==> %s", gtx.prettyPrint(), allPreparedVersions, commitVersion); } ctx.setTransactionVersion(commitVersion); } }