package org.radargun.service; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.nio.channels.spi.SelectorProvider; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import com.oracle.common.internal.net.MultiProviderSelectionService; import com.oracle.common.net.SelectionService; import com.oracle.common.net.SelectionServices; import com.tangosol.net.CacheFactory; import com.tangosol.net.MemberEvent; import com.tangosol.net.NamedCache; import com.tangosol.net.PartitionedService; import org.radargun.Service; import org.radargun.config.Converter; import org.radargun.config.Property; import org.radargun.listeners.MemberListenerImpl; import org.radargun.listeners.PartitionListenerImpl; import org.radargun.logging.Log; import org.radargun.logging.LogFactory; import org.radargun.traits.Clustered; import org.radargun.traits.Lifecycle; import org.radargun.traits.ProvidesTrait; /** * Oracle Coherence 3.x CacheWrapper implementation. * * @author Manik Surtani <msurtani@gmail.com> * @author Michal Linhard <mlinhard@redhat.com> */ @Service(doc = "Oracle Coherence 3.x CacheWrapper implementation.") public class Coherence3Service implements Lifecycle, Clustered { private Log log = LogFactory.getLog(Coherence3Service.class); protected Map<String, NamedCache> caches = new HashMap<String, NamedCache>(); protected boolean started = false; @Property(name = Service.FILE, doc = "Configuration file.", deprecatedName = "config") protected String configFile; @Property(name = "cache", doc = "Name of the default cache. Default is 'testCache'.") protected String cacheName = "testCache"; @Property(doc = "Attributes that should be indexed, in form cache:attribute,cache:attribute. By default, nothing is indexed.", converter = IndexedColumnsConverter.class) protected List<IndexedColumn> indexedColumns = Collections.EMPTY_LIST; @Property(doc = "Used to lookup the connection factory from InitialContext. By default DefaultConnectionFactory is used.") protected String connectionFactory; @Property(doc = "Service used when retrieving the connection. Default is the default service ('TransactionalCache').") protected String transactionalService; @Property(doc = "Use POF (Portable Object Format) for serialization instead of Java serialization. Default is true.") protected boolean usePOF = true; protected CoherenceQueryable queryable = new CoherenceQueryable(this); protected List<Membership> membershipHistory = new ArrayList<>(); @ProvidesTrait public Coherence3Service getSelf() { return this; } @ProvidesTrait public CoherenceOperations createOperations() { return new CoherenceOperations(this); } @ProvidesTrait public CoherenceCacheInfo createCacheInfo() { return new CoherenceCacheInfo(this); } @ProvidesTrait public CoherenceQueryable getQueryable() { return queryable; } // @ProvidesTrait // public CoherenceTransactional createTransactional() { // return new CoherenceTransactional(this); // } public NamedCache getCache(String name) { assertStarted(); if (name == null) { name = cacheName; } NamedCache nc = caches.get(name); if (nc == null) { nc = CacheFactory.getCache(name); if (nc == null) { throw new IllegalArgumentException("Cache " + name + " cannot be retrieved."); } caches.put(name, nc); queryable.registerIndices(nc, indexedColumns); if (nc.getCacheService() instanceof PartitionedService) { ((PartitionedService) nc.getCacheService()).addPartitionListener(new PartitionListenerImpl()); } log.info("Started Coherence cache " + nc.getCacheName()); } return nc; } @Override public synchronized void start() { if (usePOF) { System.setProperty("tangosol.pof.enabled", "true"); System.setProperty("tangosol.pof.config", "pof-config.xml"); } else { System.setProperty("tangosol.pof.enabled", "false"); } System.setProperty("tangosol.coherence.cacheconfig", configFile); started = true; // ensure that at least the main cache is started NamedCache cache = getCache(cacheName); // register member listener synchronized (this) { cache.getCacheService().addMemberListener(new MemberListenerImpl(this)); updateMembership(null); } log.info("Started Coherence Service"); } private void releaseCache(NamedCache nc) { log.info("Relasing cache " + nc.getCacheName()); try { CacheFactory.releaseCache(nc); } catch (IllegalStateException e) { if (e.getMessage() != null && e.getMessage().indexOf("Cache is already released") >= 0) { log.info("This cache was already destroyed by another instance"); } } } @Override public synchronized void stop() { for (NamedCache nc : caches.values()) { releaseCache(nc); } caches.clear(); CacheFactory.shutdown(); try { Field ssField = MultiProviderSelectionService.class.getDeclaredField("m_mapServices"); ssField.setAccessible(true); ConcurrentMap<SelectorProvider, SelectionService> selectionServices = (ConcurrentMap<SelectorProvider, SelectionService>) ssField.get(SelectionServices.getDefaultService()); for (SelectionService ss : selectionServices.values()) { ss.shutdown(); } } catch (Exception e) { log.error("Failed to shutdown selection services", e); } started = false; synchronized (this) { membershipHistory.add(Membership.empty()); } log.info("Cache factory was shut down."); } @Override public synchronized boolean isRunning() { return started; } @Override public boolean isCoordinator() { return false; } public synchronized void updateMembership(MemberEvent event) { List<Member> members; try { Set<com.tangosol.net.Member> memberSet = CacheFactory.ensureCluster().getMemberSet(); com.tangosol.net.Member localMember = CacheFactory.ensureCluster().getLocalMember(); members = new ArrayList<>(memberSet.size()); for (com.tangosol.net.Member m : memberSet) { members.add(new Member(String.format("%d@%s[%s]", m.getId(), m.getMachineName(), m.getAddress().getHostName()), localMember.equals(m), false)); } } catch (IllegalStateException ise) { if (ise.getMessage().indexOf("SafeCluster has been explicitly stopped") >= 0) { log.info("The cluster is stopped."); members = Collections.EMPTY_LIST; } else { throw ise; } } if (members.equals(getMembers())) { log.trace("No change in membership: " + members + " -> " + getMembers()); return; } membershipHistory.add(Membership.create(members)); } @Override public synchronized Collection<Member> getMembers() { if (membershipHistory.isEmpty()) return null; return membershipHistory.get(membershipHistory.size() - 1).members; } @Override public synchronized List<Membership> getMembershipHistory() { return new ArrayList<>(membershipHistory); } protected void assertStarted() { if (!started) throw new IllegalStateException("Cache is not started"); } protected static class IndexedColumn { public final String cache; public final String attribute; public final boolean ordered; public IndexedColumn(String cache, String attribute, boolean ordered) { this.cache = cache; this.attribute = attribute; this.ordered = ordered; } @Override public String toString() { return String.format("[cache=%s, attribute=%s, sorted=%s]", cache, attribute, ordered); } } private static class IndexedColumnsConverter implements Converter<List<IndexedColumn>> { @Override public List<IndexedColumn> convert(String string, Type type) { String[] parts = string.split("(,|\\n)"); ArrayList<IndexedColumn> list = new ArrayList<IndexedColumn>(parts.length); for (String part : parts) { String[] ca = part.split(":"); if (ca.length == 2) { list.add(new IndexedColumn(ca[0].trim(), ca[1].trim(), false)); } else if (ca.length == 3) { list.add(new IndexedColumn(ca[0].trim(), ca[1].trim(), ca[2].trim().equalsIgnoreCase("ordered"))); } else { throw new IllegalArgumentException(string); } } return list; } @Override public String convertToString(List<IndexedColumn> value) { StringBuilder sb = new StringBuilder(); for (IndexedColumn c : value) { if (sb.length() > 0) sb.append(", "); sb.append(c.toString()); } return sb.toString(); } @Override public String allowedPattern(Type type) { return ".*:.*(:ordered)?((,|\\n).*:.*(:ordered)?)*"; } } }