mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-14 06:18:12 +00:00
major refactoring introducing tagging the replication streams with the Replicable IDs and introducing a new URL parameter for the initial load servlet request
This commit is contained in:
1 parent
401da5f50b
commit
bb5a16e1ac
28 files changed
+648
-370
No files matched your search
@@ -17,6 +17,7 @@ Require-Bundle: com.sap.sailing.domain,
|
||||
org.objenesis;bundle-version="1.3.0",
|
||||
org.junit4;bundle-version="4.8.2",
|
||||
org.json.simple;bundle-version="1.1.0",
|
||||
com.sap.sse.common
|
||||
com.sap.sse.common,
|
||||
com.sap.sse.replication
|
||||
Export-Package: com.sap.sailing.domain.test,
|
||||
com.sap.sailing.domain.test.mock
|
||||
@@ -34,4 +34,5 @@ Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0",
|
||||
com.sap.sailing.geocoding,
|
||||
com.sap.sse.datamining.shared;bundle-version="1.0.0",
|
||||
com.sap.sse.common,
|
||||
com.sap.sse
|
||||
com.sap.sse,
|
||||
com.sap.sse.replication
|
||||
@@ -22,6 +22,7 @@ import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.replication.impl.ObjectInputStreamResolvingAgainstCache;
|
||||
|
||||
public interface DomainFactory extends SharedDomainFactory {
|
||||
/**
|
||||
@@ -38,7 +39,7 @@ public interface DomainFactory extends SharedDomainFactory {
|
||||
* de-serialized instances need to be replaced by / resolved to the counterparts already known by this factory.
|
||||
* The stream returned by this method can be used
|
||||
*/
|
||||
ObjectInputStreamResolvingAgainstDomainFactory createObjectInputStreamResolvingAgainstThisFactory(InputStream inputStream) throws IOException;
|
||||
ObjectInputStreamResolvingAgainstCache<DomainFactory> createObjectInputStreamResolvingAgainstThisFactory(InputStream inputStream) throws IOException;
|
||||
|
||||
ScoringScheme createScoringScheme(ScoringSchemeType scoringSchemeType);
|
||||
|
||||
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectStreamClass;
|
||||
|
||||
import com.sap.sse.common.IsManagedByCache;
|
||||
|
||||
/**
|
||||
* During de-serialization, resolves objects managed by a {@link DomainFactory} using the domain factory instance passed
|
||||
* to the constructor. Additionally, class loading is managed using the current thread's context class loader which is
|
||||
* required in and works well for OSGi environments.
|
||||
* <p>
|
||||
*
|
||||
* Create an instance of this class using
|
||||
* {@link DomainFactory#createObjectInputStreamResolvingAgainstThisFactory(InputStream)}.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public abstract class ObjectInputStreamResolvingAgainstDomainFactory extends ObjectInputStream implements HasDomainFactory {
|
||||
private final DomainFactory domainFactory;
|
||||
|
||||
/**
|
||||
* Package protected on purpose; instances to be created using
|
||||
* {@link DomainFactory#createObjectInputStreamResolvingAgainstThisFactory(InputStream)}. This constructor
|
||||
* {@link #enableResolveObject(boolean) enables resolving} by default.
|
||||
*/
|
||||
protected ObjectInputStreamResolvingAgainstDomainFactory(InputStream in, DomainFactory domainFactory) throws IOException {
|
||||
super(in);
|
||||
this.domainFactory = domainFactory;
|
||||
enableResolveObject(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainFactory getDomainFactory() {
|
||||
return domainFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException {
|
||||
String className = classDesc.getName();
|
||||
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6554519
|
||||
// If using loadClass(...) on the class loader directly, an exception is thrown:
|
||||
// StreamCorruptedException: invalid type code 00
|
||||
return Class.forName(className, /* initialize */ true, Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveObject(Object o) {
|
||||
Object result;
|
||||
if (o instanceof IsManagedByCache) {
|
||||
@SuppressWarnings("unchecked")
|
||||
IsManagedByCache<SharedDomainFactory> castResult = ((IsManagedByCache<SharedDomainFactory>) o).resolve(getDomainFactory());
|
||||
result = castResult;
|
||||
} else {
|
||||
result = o;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -13,7 +13,6 @@ import com.sap.sailing.domain.base.CompetitorStore;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.ObjectInputStreamResolvingAgainstDomainFactory;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
@@ -61,6 +60,7 @@ import com.sap.sailing.geocoding.ReverseGeocoder;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sse.replication.impl.ObjectInputStreamResolvingAgainstCache;
|
||||
|
||||
public class DomainFactoryImpl extends SharedDomainFactoryImpl implements DomainFactory {
|
||||
private static Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName());
|
||||
@@ -82,7 +82,7 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectInputStreamResolvingAgainstDomainFactory createObjectInputStreamResolvingAgainstThisFactory(InputStream inputStream) throws IOException {
|
||||
public ObjectInputStreamResolvingAgainstCache<DomainFactory> createObjectInputStreamResolvingAgainstThisFactory(InputStream inputStream) throws IOException {
|
||||
return new ObjectInputStreamResolvingAgainstDomainFactoryImpl(inputStream, this);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.ObjectInputStreamResolvingAgainstDomainFactory;
|
||||
import com.sap.sse.replication.impl.ObjectInputStreamResolvingAgainstCache;
|
||||
|
||||
public class ObjectInputStreamResolvingAgainstDomainFactoryImpl extends ObjectInputStreamResolvingAgainstDomainFactory {
|
||||
public class ObjectInputStreamResolvingAgainstDomainFactoryImpl extends ObjectInputStreamResolvingAgainstCache<DomainFactory> {
|
||||
ObjectInputStreamResolvingAgainstDomainFactoryImpl(InputStream in, DomainFactory domainFactory)
|
||||
throws IOException {
|
||||
super(in, domainFactory);
|
||||
|
||||
+13
-17
@@ -1,12 +1,12 @@
|
||||
package com.sap.sailing.gwt.ui.test;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.bind.JAXBException;
|
||||
@@ -33,11 +33,11 @@ import com.sap.sailing.domain.tracking.TrackerManager;
|
||||
import com.sap.sailing.domain.tractracadapter.TracTracAdapterFactory;
|
||||
import com.sap.sailing.gwt.ui.server.SailingServiceImpl;
|
||||
import com.sap.sailing.server.RacingEventService;
|
||||
import com.sap.sailing.server.RacingEventServiceOperation;
|
||||
import com.sap.sailing.server.impl.RacingEventServiceImpl;
|
||||
import com.sap.sailing.xrr.resultimport.schema.RegattaResults;
|
||||
import com.sap.sse.replication.Replicable;
|
||||
import com.sap.sse.replication.ReplicationService;
|
||||
import com.sap.sse.replication.impl.AbstractReplicablesProvider;
|
||||
import com.sap.sse.replication.impl.ReplicationInstancesManager;
|
||||
import com.sap.sse.replication.impl.ReplicationServiceImpl;
|
||||
|
||||
@@ -60,24 +60,20 @@ public class SailingServiceImplMock extends SailingServiceImpl {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServiceTracker<ReplicationService<RacingEventService>, ReplicationService<RacingEventService>> createAndOpenReplicationServiceTracker(
|
||||
protected ServiceTracker<ReplicationService, ReplicationService> createAndOpenReplicationServiceTracker(
|
||||
BundleContext context) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ServiceTracker<ReplicationService<RacingEventService>, ReplicationService<RacingEventService>> result = mock(ServiceTracker.class);
|
||||
ServiceTracker<ReplicationService, ReplicationService> result = mock(ServiceTracker.class);
|
||||
try {
|
||||
final ReplicationServiceImpl<RacingEventService, RacingEventServiceOperation<?>> replicationService =
|
||||
new ReplicationServiceImpl<RacingEventService, RacingEventServiceOperation<?>>("test exchange", "localhost", 0,
|
||||
new ReplicationInstancesManager<RacingEventService>()) {
|
||||
@Override
|
||||
protected ServiceTracker<Replicable<RacingEventService, RacingEventServiceOperation<?>>, Replicable<RacingEventService, RacingEventServiceOperation<?>>> getReplicableTracker() {
|
||||
@SuppressWarnings("unchecked")
|
||||
ServiceTracker<Replicable<RacingEventService, RacingEventServiceOperation<?>>, Replicable<RacingEventService, RacingEventServiceOperation<?>>> result =
|
||||
(ServiceTracker<Replicable<RacingEventService, RacingEventServiceOperation<?>>, Replicable<RacingEventService, RacingEventServiceOperation<?>>>) mock(ServiceTracker.class);
|
||||
doReturn("Humba Humba").when(result).toString();
|
||||
when(result.getService()).thenReturn(new RacingEventServiceImpl());
|
||||
return result;
|
||||
}
|
||||
};
|
||||
final RacingEventService replicable = new RacingEventServiceImpl();
|
||||
final ReplicationServiceImpl replicationService =
|
||||
new ReplicationServiceImpl("test exchange", "localhost", 0,
|
||||
new ReplicationInstancesManager(), new AbstractReplicablesProvider() {
|
||||
@Override
|
||||
public Iterable<Replicable<?, ?>> getReplicables() {
|
||||
return Collections.<Replicable<?, ?>>singleton(replicable);
|
||||
}
|
||||
});
|
||||
when(result.getService()).thenReturn(replicationService);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException();
|
||||
|
||||
+8
-8
@@ -419,7 +419,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
|
||||
private final ServiceTracker<RacingEventService, RacingEventService> racingEventServiceTracker;
|
||||
|
||||
private final ServiceTracker<ReplicationService<RacingEventService>, ReplicationService<RacingEventService>> replicationServiceTracker;
|
||||
private final ServiceTracker<ReplicationService, ReplicationService> replicationServiceTracker;
|
||||
|
||||
private final ServiceTracker<ResultUrlRegistry, ResultUrlRegistry> resultUrlRegistryServiceTracker;
|
||||
|
||||
@@ -662,10 +662,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
return new ScoreCorrectionProviderDTO(scoreCorrectionProvider.getName(), hasResultsForBoatClassFromDateByEventName);
|
||||
}
|
||||
|
||||
protected ServiceTracker<ReplicationService<RacingEventService>, ReplicationService<RacingEventService>> createAndOpenReplicationServiceTracker(
|
||||
protected ServiceTracker<ReplicationService, ReplicationService> createAndOpenReplicationServiceTracker(
|
||||
BundleContext context) {
|
||||
ServiceTracker<ReplicationService<RacingEventService>, ReplicationService<RacingEventService>> result =
|
||||
new ServiceTracker<ReplicationService<RacingEventService>, ReplicationService<RacingEventService>>(
|
||||
ServiceTracker<ReplicationService, ReplicationService> result =
|
||||
new ServiceTracker<ReplicationService, ReplicationService>(
|
||||
context, ReplicationService.class.getName(), null);
|
||||
result.open();
|
||||
return result;
|
||||
@@ -2206,7 +2206,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
return racingEventServiceTracker.getService(); // grab the service
|
||||
}
|
||||
|
||||
private ReplicationService<RacingEventService> getReplicationService() {
|
||||
private ReplicationService getReplicationService() {
|
||||
return replicationServiceTracker.getService();
|
||||
}
|
||||
|
||||
@@ -3193,12 +3193,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
|
||||
@Override
|
||||
public ReplicationStateDTO getReplicaInfo() {
|
||||
ReplicationService<RacingEventService> service = getReplicationService();
|
||||
ReplicationService service = getReplicationService();
|
||||
Set<ReplicaDTO> replicaDTOs = new HashSet<ReplicaDTO>();
|
||||
for (ReplicaDescriptor replicaDescriptor : service.getReplicaInfo()) {
|
||||
final Map<Class<? extends OperationWithResult<RacingEventService, ?>>, Integer> statistics = service.getStatistics(replicaDescriptor);
|
||||
final Map<Class<? extends OperationWithResult<?, ?>>, Integer> statistics = service.getStatistics(replicaDescriptor);
|
||||
Map<String, Integer> replicationCountByOperationClassName = new HashMap<String, Integer>();
|
||||
for (Entry<Class<? extends OperationWithResult<RacingEventService, ?>>, Integer> e : statistics.entrySet()) {
|
||||
for (Entry<Class<? extends OperationWithResult<?, ?>>, Integer> e : statistics.entrySet()) {
|
||||
replicationCountByOperationClassName.put(e.getKey().getName(), e.getValue());
|
||||
}
|
||||
replicaDTOs.add(new ReplicaDTO(replicaDescriptor.getIpAddress().getHostName(), replicaDescriptor
|
||||
|
||||
+11
-20
@@ -39,6 +39,7 @@ import com.sap.sailing.server.RacingEventServiceOperation;
|
||||
import com.sap.sailing.server.impl.RacingEventServiceImpl;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.mongodb.MongoDBService;
|
||||
import com.sap.sse.replication.Replicable;
|
||||
import com.sap.sse.replication.ReplicationMasterDescriptor;
|
||||
import com.sap.sse.replication.ReplicationService;
|
||||
import com.sap.sse.replication.impl.Activator;
|
||||
@@ -56,7 +57,7 @@ public abstract class AbstractServerReplicationTest {
|
||||
protected RacingEventServiceImpl master;
|
||||
protected ReplicationServiceTestImpl replicaReplicator;
|
||||
private ReplicaDescriptor replicaDescriptor;
|
||||
private ReplicationServiceImpl<RacingEventService, RacingEventServiceOperation<?>> masterReplicator;
|
||||
private ReplicationServiceImpl masterReplicator;
|
||||
private ReplicationMasterDescriptor masterDescriptor;
|
||||
|
||||
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(5 * 60 * 1000); // timeout after 5 minutes
|
||||
@@ -167,17 +168,17 @@ public abstract class AbstractServerReplicationTest {
|
||||
replicaReplicator.stopToReplicateFromMaster();
|
||||
}
|
||||
|
||||
static class ReplicationServiceTestImpl extends ReplicationServiceImpl<RacingEventService, RacingEventServiceOperation<?>> {
|
||||
static class ReplicationServiceTestImpl extends ReplicationServiceImpl {
|
||||
protected static final int INITIAL_LOAD_PACKAGE_SIZE = 1024*1024;
|
||||
private final DomainFactory resolveAgainst;
|
||||
private final RacingEventService master;
|
||||
private final ReplicaDescriptor replicaDescriptor;
|
||||
private final ReplicationService<RacingEventService> masterReplicationService;
|
||||
private final ReplicationService masterReplicationService;
|
||||
private final ReplicationMasterDescriptor masterDescriptor;
|
||||
|
||||
public ReplicationServiceTestImpl(String exchangeName, String exchangeHost, DomainFactory resolveAgainst,
|
||||
ReplicationInstancesManager<RacingEventService> replicationInstancesManager, ReplicaDescriptor replicaDescriptor,
|
||||
RacingEventService replica, RacingEventService master, ReplicationService<RacingEventService> masterReplicationService,
|
||||
ReplicationInstancesManager replicationInstancesManager, ReplicaDescriptor replicaDescriptor,
|
||||
RacingEventService replica, RacingEventService master, ReplicationService masterReplicationService,
|
||||
ReplicationMasterDescriptor masterDescriptor)
|
||||
throws IOException {
|
||||
super(exchangeName, exchangeHost, replicationInstancesManager, replica);
|
||||
@@ -248,17 +249,6 @@ public abstract class AbstractServerReplicationTest {
|
||||
return initialLoadTestServerThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore the master descriptor and replicate from the local master passed to the constructor instead.
|
||||
*/
|
||||
// @Override
|
||||
// public void startToReplicateFrom(ReplicationMasterDescriptor master) throws IOException,
|
||||
// ClassNotFoundException {
|
||||
// Replicator replicator = startToReplicateFromButDontYetFetchInitialLoad(master, /* startReplicatorSuspended */ true);
|
||||
// initialLoad();
|
||||
// replicator.setSuspended(false); // resume after initial load
|
||||
// }
|
||||
|
||||
protected Replicator<RacingEventService, RacingEventServiceOperation<?>> startToReplicateFromButDontYetFetchInitialLoad(ReplicationMasterDescriptor master, boolean startReplicatorSuspended)
|
||||
throws IOException {
|
||||
masterReplicationService.registerReplica(replicaDescriptor);
|
||||
@@ -291,10 +281,11 @@ public abstract class AbstractServerReplicationTest {
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
ObjectInputStream dis = resolveAgainst.createObjectInputStreamResolvingAgainstThisFactory(pis);
|
||||
getReplicable().clearReplicaState();
|
||||
getReplicable().initiallyFillFrom(dis);
|
||||
dis.close();
|
||||
for (Replicable<?, ?> replicable : getReplicables()) {
|
||||
replicable.clearReplicaState();
|
||||
replicable.initiallyFillFrom(pis);
|
||||
}
|
||||
pis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,29 +69,37 @@ import com.sap.sse.common.Util.Triple;
|
||||
import com.sap.sse.common.search.KeywordQuery;
|
||||
import com.sap.sse.common.search.Result;
|
||||
import com.sap.sse.common.search.Searchable;
|
||||
import com.sap.sse.replication.Replicable;
|
||||
import com.sap.sse.replication.impl.ReplicableWithObjectInputStream;
|
||||
|
||||
/**
|
||||
* An OSGi service that can be used to track boat races using a TracTrac connector that pushes
|
||||
* live GPS boat location, waypoint, coarse and mark passing data.<p>
|
||||
* An OSGi service that can be used to track boat races using a TracTrac connector that pushes live GPS boat location,
|
||||
* waypoint, coarse and mark passing data.
|
||||
* <p>
|
||||
*
|
||||
* If a race/regatta is already being tracked, another {@link #addTracTracRace(URL, URI, URI, WindStore, long)} or
|
||||
* {@link #addRegatta(URL, URI, URI, WindStore, long)} call will have no effect, even if a different
|
||||
* {@link WindStore} is requested.<p>
|
||||
* {@link #addRegatta(URL, URI, URI, WindStore, long)} call will have no effect, even if a different {@link WindStore}
|
||||
* is requested.
|
||||
* <p>
|
||||
*
|
||||
* When the tracking of a race/regatta is {@link #stopTracking(Regatta, RaceDefinition) stopped}, the next
|
||||
* time it's started to be tracked, a new {@link TrackedRace} at least will be constructed. This also
|
||||
* means that when a {@link TrackedRegatta} exists that still holds other {@link TrackedRace}s, the
|
||||
* no longer tracked {@link TrackedRace} will be removed from the {@link TrackedRegatta}.
|
||||
* corresponding information is removed also from the {@link DomainFactory}'s caches to ensure that
|
||||
* clean, fresh data is received should another tracking request be issued later.
|
||||
* When the tracking of a race/regatta is {@link #stopTracking(Regatta, RaceDefinition) stopped}, the next time it's
|
||||
* started to be tracked, a new {@link TrackedRace} at least will be constructed. This also means that when a
|
||||
* {@link TrackedRegatta} exists that still holds other {@link TrackedRace}s, the no longer tracked {@link TrackedRace}
|
||||
* will be removed from the {@link TrackedRegatta}. corresponding information is removed also from the
|
||||
* {@link DomainFactory}'s caches to ensure that clean, fresh data is received should another tracking request be issued
|
||||
* later.
|
||||
* <p>
|
||||
*
|
||||
* During receiving the initial load for a replication in {@link #initiallyFillFromInternal(java.io.ObjectInputStream)},
|
||||
* tracked regattas read from the stream are observed (see {@link RaceListener}) by this object for automatic updates to
|
||||
* the default leaderboard and for automatic linking to leaderboard columns. It is assumed that no explicit replication
|
||||
* of these operations will happen based on the changes performed on the replication master.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetcher, RegattaRegistry, RaceFetcher,
|
||||
LeaderboardRegistry, EventResolver, LeaderboardGroupResolver, TrackerManager, Searchable<LeaderboardSearchResult, KeywordQuery>,
|
||||
Replicable<RacingEventService, RacingEventServiceOperation<?>> {
|
||||
ReplicableWithObjectInputStream<RacingEventService, RacingEventServiceOperation<?>> {
|
||||
@Override
|
||||
Regatta getRegatta(RegattaName regattaName);
|
||||
|
||||
|
||||
+3
-5
@@ -325,8 +325,7 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
|
||||
/**
|
||||
* This author should be used for server generated race log events
|
||||
*/
|
||||
private final RaceLogEventAuthor raceLogEventAuthorForServer = new RaceLogEventAuthorImpl(
|
||||
RacingEventService.class.getName(), 0);
|
||||
private final RaceLogEventAuthor raceLogEventAuthorForServer = new RaceLogEventAuthorImpl(RacingEventService.class.getName(), 0);
|
||||
|
||||
/**
|
||||
* Allow only one master data import at a time to avoid situation where multiple Imports override each other in
|
||||
@@ -2129,7 +2128,7 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeForInitialReplication(ObjectOutputStream oos) throws IOException {
|
||||
public void serializeForInitialReplicationInternal(ObjectOutputStream oos) throws IOException {
|
||||
StringBuffer logoutput = new StringBuffer();
|
||||
|
||||
logger.info("Serializing events...");
|
||||
@@ -2191,8 +2190,7 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
|
||||
@SuppressWarnings("unchecked") // all the casts of ois.readObject()'s return value to Map<..., ...>
|
||||
// the type-parameters in the casts of the de-serialized collection objects can't be checked
|
||||
@Override
|
||||
public void initiallyFillFrom(ObjectInputStream ois) throws IOException, ClassNotFoundException,
|
||||
InterruptedException {
|
||||
public void initiallyFillFromInternal(ObjectInputStream ois) throws IOException, ClassNotFoundException, InterruptedException {
|
||||
logger.info("Performing initial replication load on " + this);
|
||||
ClassLoader oldContextClassloader = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
|
||||
+2
-2
@@ -9,7 +9,6 @@ import java.util.UUID;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.CompetitorStore;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.ObjectInputStreamResolvingAgainstDomainFactory;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.impl.MasterDataImportInformation;
|
||||
import com.sap.sailing.domain.base.impl.RegattaImpl;
|
||||
@@ -20,6 +19,7 @@ import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
|
||||
import com.sap.sailing.domain.racelog.RaceLogStore;
|
||||
import com.sap.sailing.server.RacingEventService;
|
||||
import com.sap.sailing.server.operationaltransformation.ImportMasterDataOperation;
|
||||
import com.sap.sse.replication.impl.ObjectInputStreamResolvingAgainstCache;
|
||||
|
||||
public class MasterDataImporter {
|
||||
private final DomainFactory baseDomainFactory;
|
||||
@@ -33,7 +33,7 @@ public class MasterDataImporter {
|
||||
|
||||
public void importFromStream(InputStream inputStream, UUID importOperationId, boolean override) throws IOException,
|
||||
ClassNotFoundException {
|
||||
ObjectInputStreamResolvingAgainstDomainFactory objectInputStream = racingEventService.getBaseDomainFactory()
|
||||
ObjectInputStreamResolvingAgainstCache<DomainFactory> objectInputStream = racingEventService.getBaseDomainFactory()
|
||||
.createObjectInputStreamResolvingAgainstThisFactory(inputStream);
|
||||
racingEventService
|
||||
.createOrUpdateDataImportProgressWithReplication(importOperationId, 0.03, "Reading Data", 0.5);
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import org.osgi.framework.BundleContext;
|
||||
@@ -11,33 +12,46 @@ import org.osgi.framework.BundleContext;
|
||||
import com.sap.sse.common.WithID;
|
||||
import com.sap.sse.operationaltransformation.Operation;
|
||||
import com.sap.sse.operationaltransformation.OperationWithTransformationSupport;
|
||||
import com.sap.sse.replication.impl.ObjectInputStreamResolvingAgainstCache;
|
||||
|
||||
/**
|
||||
* Represents a replicable part of an application. Such a replicable part is usually holder of the application state and
|
||||
* central transaction / operation processing unit. Processing {@link Operation operations} generally happens by
|
||||
* {@link Operation#applyTo(Object) applying} operations to this replicable. As such, this replicable plays the
|
||||
* role of the <code>toState</code> argument in the {@link Operation#applyTo(Object)} method calls.<p>
|
||||
* Represents a replicable part of an application. Such a replicable part is usually holder of application state and a
|
||||
* transaction / operation processing unit. Processing {@link Operation operations} generally happens by
|
||||
* {@link Operation#applyTo(Object) applying} operations to this replicable. As such, this replicable plays the role of
|
||||
* the <code>toState</code> argument in the {@link Operation#applyTo(Object)} method calls.
|
||||
* <p>
|
||||
*
|
||||
* In addition to being the operation processor, a replicable must be able to serialize its state for the initial
|
||||
* load of a replica, and it must be able to initialize its state from this serialized form.<p>
|
||||
* In addition to being the operation processor, a replicable must be able to serialize its state for the initial load
|
||||
* of a replica, and it must be able to initialize its state from this serialized form. In doing so it is important to
|
||||
* not include state that is controlled by another replicable. References to such objects need to be <em>transient</em>
|
||||
* which means they are not serialized together with the initial load of this replica. If such references need to be
|
||||
* re-established after de-serialization then they need to represented by some key which can later be looked up in the
|
||||
* other replicable which can be discovered through the OSGi service registry on the receiving end.
|
||||
* <p>
|
||||
*
|
||||
* For operation and initial load serialization, a replicable must provide a specialized {@link ObjectInputStream} that
|
||||
* ensures that on the receiving end there are no two Java object copies representing the same domain object where this
|
||||
* matters. This is particularly important for objects with mutable state that otherwise may run inconsistent when only
|
||||
* one of the multiple copies gets modified. Typically, the object input stream uses some sort of cache and overrides
|
||||
* the {@link ObjectInputStream#resolveObject} method, and the types whose objects are de-serialized need to co-operate by
|
||||
* For operation and initial load serialization, a replicable is provided an {@link InputStream} from which to read the
|
||||
* operations and the initial load. A typical implementation for reading the stream's contents will use a specialized
|
||||
* {@link ObjectInputStream} such as a specialization of {@link ObjectInputStreamResolvingAgainstCache} that ensures
|
||||
* that on the receiving end there are no two Java object copies representing the same domain object where this matters.
|
||||
* This is particularly important for objects with mutable state that otherwise may run inconsistent when only one of
|
||||
* the multiple copies gets modified. Typically, the object input stream uses some sort of cache and overrides the
|
||||
* {@link ObjectInputStream#resolveObject} method, and the types whose objects are de-serialized need to co-operate by
|
||||
* implementing a marker interface telling the specialized object input stream to use the cache when resolving objects
|
||||
* of that type.<p>
|
||||
* of that type.
|
||||
* <p>
|
||||
*
|
||||
* Several {@link Replicable} instances can share the same replication service and the same set of replication channels.
|
||||
* In order to participate in replication, {@link Replicable} objects need to be registered as OSGi services for the
|
||||
* {@link Replicable} interface. The replication service will dynamically discover the {@link Replicable} objects when
|
||||
* initial load is requested and when an {@link Operation} is received. The initial load streams as well as each replication
|
||||
* operation need to identify the {@link Replicable} they refer to. This identification is added as an OSGi service registry
|
||||
* parameter that is used during service discovery.
|
||||
* initial load is requested and when an {@link Operation} is received. The initial load streams as well as each
|
||||
* replication operation need to identify the {@link Replicable} they refer to. This identification is added as an OSGi
|
||||
* service registry parameter that is used during service discovery.
|
||||
*
|
||||
* @param <S> the type of state to which the operations are applied; usually this will be set to the implementing subclass
|
||||
* @param <O> type of operation that the replicable accepts
|
||||
* @param <S>
|
||||
* the type of state to which the operations are applied; usually this will be set to the implementing
|
||||
* subclass
|
||||
* @param <O>
|
||||
* type of operation that the replicable accepts
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
@@ -51,12 +65,6 @@ public interface Replicable<S, O extends OperationWithResult<S, ?>> extends With
|
||||
*/
|
||||
final String OSGi_Service_Registry_ID_Property_Name = "ID";
|
||||
|
||||
/**
|
||||
* Produces an object input stream that can choose to resolve objects against a cache so that duplicate instances
|
||||
* are avoided.
|
||||
*/
|
||||
ObjectInputStream createObjectInputStreamResolvingAgainstCache(InputStream is) throws IOException;
|
||||
|
||||
/**
|
||||
* Executes an operation whose effects need to be replicated to any replica of this service known and
|
||||
* {@link OperationExecutionListener#executed(OperationWithTransformationSupport) notifies} all registered
|
||||
@@ -64,6 +72,11 @@ public interface Replicable<S, O extends OperationWithResult<S, ?>> extends With
|
||||
*/
|
||||
<T> T apply(OperationWithResult<S, T> operation);
|
||||
|
||||
/**
|
||||
* An operation execution listener must be able to process notifications of operations being executed that have
|
||||
* type <code>S</code> or any more specific type. The listener can achieve this also by accepting any type more general than
|
||||
* <code>S</code>.
|
||||
*/
|
||||
void addOperationExecutionListener(OperationExecutionListener<S> listener);
|
||||
|
||||
void removeOperationExecutionListener(OperationExecutionListener<S> listener);
|
||||
@@ -80,27 +93,32 @@ public interface Replicable<S, O extends OperationWithResult<S, ?>> extends With
|
||||
void clearReplicaState() throws MalformedURLException, IOException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Dual, reading operation for {@link #serializeForInitialReplication(ObjectOutputStream)}. In other words, when
|
||||
* this operation returns, this service instance is in a state "equivalent" to that of the service instance that
|
||||
* produced the stream contents in its {@link #serializeForInitialReplication(ObjectOutputStream)}. "Equivalent"
|
||||
* here means that a replica will have equal sets of regattas, tracked regattas, leaderboards and leaderboard groups but
|
||||
* will not have any active trackers for wind or positions because it relies on these elements to be sent through
|
||||
* the replication channel.
|
||||
* Dual, reading operation for {@link #serializeForInitialReplication(OutputStream)}. In other words, when this
|
||||
* operation returns, this service instance is in a state "equivalent" to that of the service instance that produced
|
||||
* the stream contents in its {@link #serializeForInitialReplication(OutputStream)}. "Equivalent" here means that a
|
||||
* replica will have equal sets of objects serialized in the initial load but will usually not have any transient
|
||||
* processes replicated that are responsible for building and maintaining the state on the master side because it
|
||||
* relies on these elements to be sent through the replication channel.
|
||||
* <p>
|
||||
*
|
||||
* Tracked regattas read from the stream are observed (see {@link RaceListener}) by this object for automatic updates
|
||||
* to the default leaderboard and for automatic linking to leaderboard columns. It is assumed that no explicit
|
||||
* replication of these operations will happen based on the changes performed on the replication master.<p>
|
||||
*
|
||||
* <b>Caution:</b> All relevant contents of this service instance needs to be cleared before by a call to
|
||||
* {@link #clearReplicaState()}. It will be replaced by the stream contents.
|
||||
*/
|
||||
void initiallyFillFrom(ObjectInputStream ois) throws IOException, ClassNotFoundException, InterruptedException;
|
||||
void initiallyFillFrom(InputStream is) throws IOException, ClassNotFoundException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Produces a one-shot serializable copy of those elements required for replication into <code>oos</code> so that
|
||||
* afterwards the {@link RacingEventServiceOperation}s can be {@link #apply(RacingEventServiceOperation) applied} to
|
||||
* maintain consistency with the master copy of the service. The dual operation is {@link #initiallyFillFrom}.
|
||||
* Produces a one-shot serializable copy of those elements required for replication into <code>os</code> by wrapping
|
||||
* that stream by an {@link ObjectOutputStream} so that afterwards the {@link OperationWithResult}s can be
|
||||
* {@link #apply(OperationWithResult) applied} to maintain consistency with the master copy of the service. The dual
|
||||
* operation is {@link #initiallyFillFrom}.
|
||||
*/
|
||||
void serializeForInitialReplication(ObjectOutputStream oos) throws IOException;
|
||||
void serializeForInitialReplication(OutputStream os) throws IOException;
|
||||
|
||||
/**
|
||||
* From an input stream, reads an operation that can be {@link #apply(OperationWithResult) applied} to this object.
|
||||
* Separating reading and applying gives clients an opportunity to queue operations, e.g., in order to wait until
|
||||
* receiving and {@link #initiallyFillFrom(InputStream) filling} the initial load has completed.
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
O readOperation(InputStream inputStream) throws IOException, ClassNotFoundException;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.sap.sse.replication;
|
||||
|
||||
|
||||
public interface ReplicablesProvider {
|
||||
/**
|
||||
* Listeners of this type can be registered and de-registered using
|
||||
* {@link ReplicablesProvider#addReplicableLifeCycleListener(ReplicableLifeCycleListener)} and
|
||||
* {@link ReplicablesProvider#removeReplicableLifeCycleListener(ReplicableLifeCycleListener)}. They will be notified of
|
||||
* {@link Replicable}s being added to or removed from this server instance. A replication service may use this to
|
||||
* start listening to operations executed by a new {@link Replicable} or stop observing one that is being removed.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public static interface ReplicableLifeCycleListener {
|
||||
void replicableAdded(Replicable<?, ?> replicable);
|
||||
void replicableRemoved(Replicable<?, ?> replicable);
|
||||
}
|
||||
|
||||
Iterable<Replicable<?, ?>> getReplicables();
|
||||
|
||||
/**
|
||||
* Tries to obtain a {@link Replicable> by its {@link Replicable#getId() ID}, converted to a string using {@link Object#toString()}
|
||||
* on the ID.
|
||||
*
|
||||
* @return <code>null</code> if no such replicable can be found; the replicable otherwise
|
||||
*/
|
||||
Replicable<?, ?> getReplicable(String replicableIdAsString);
|
||||
|
||||
void addReplicableLifeCycleListener(ReplicableLifeCycleListener listener);
|
||||
|
||||
void removeReplicableLifeCycleListener(ReplicableLifeCycleListener listener);
|
||||
}
|
||||
+6
-3
@@ -23,10 +23,13 @@ public interface ReplicationMasterDescriptor {
|
||||
URL getReplicationDeRegistrationRequestURL(UUID uuid) throws MalformedURLException;
|
||||
|
||||
/**
|
||||
* The content produced by the URL returned is the name of the queue from which the client can read the
|
||||
* initial load using a {@link RabbitInputStreamProvider}.
|
||||
* The content produced by the URL returned is the name of the queue from which the client can read the initial load
|
||||
* using a {@link RabbitInputStreamProvider}.
|
||||
*
|
||||
* @param replicables
|
||||
* the replicables for which to request the initial load
|
||||
*/
|
||||
URL getInitialLoadURL() throws MalformedURLException;
|
||||
URL getInitialLoadURL(Iterable<Replicable<?, ?>> replicables) throws MalformedURLException;
|
||||
|
||||
int getMessagingPort();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.sap.sse.replication.impl.ReplicationServlet;
|
||||
*
|
||||
* @param <S> the type representing the master's state
|
||||
*/
|
||||
public interface ReplicationService<S> {
|
||||
public interface ReplicationService {
|
||||
static String SAILING_SERVER_REPLICATION_TOPIC = "SailingServerReplicationTopic";
|
||||
|
||||
/**
|
||||
@@ -57,7 +57,7 @@ public interface ReplicationService<S> {
|
||||
* For a replica replicating off this master, provides statistics in the form of number of operations sent to that
|
||||
* replica by type, where the operation type is the key, represented as the operation's class name
|
||||
*/
|
||||
Map<Class<? extends OperationWithResult<S, ?>>, Integer> getStatistics(ReplicaDescriptor replicaDescriptor);
|
||||
Map<Class<? extends OperationWithResult<?, ?>>, Integer> getStatistics(ReplicaDescriptor replicaDescriptor);
|
||||
|
||||
double getAverageNumberOfOperationsPerMessage(ReplicaDescriptor replicaDescriptor);
|
||||
|
||||
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
package com.sap.sse.replication.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import com.sap.sse.replication.Replicable;
|
||||
import com.sap.sse.replication.ReplicablesProvider;
|
||||
|
||||
public abstract class AbstractReplicablesProvider implements ReplicablesProvider {
|
||||
private final Set<ReplicableLifeCycleListener> listeners;
|
||||
|
||||
public AbstractReplicablesProvider() {
|
||||
this.listeners = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addReplicableLifeCycleListener(ReplicableLifeCycleListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeReplicableLifeCycleListener(ReplicableLifeCycleListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* This default implementation loops over the result of {@link #getReplicables()} and returns the first object whose
|
||||
* {@link Replicable#getId() ID} equals the value of the parameter <code>replicableIdAsString</code>.
|
||||
*
|
||||
* @throws NoSuchElementException if no replicable by that name is found
|
||||
*/
|
||||
@Override
|
||||
public Replicable<?, ?> getReplicable(String replicableIdAsString) {
|
||||
return StreamSupport.stream(getReplicables().spliterator(), /* parallel */false)
|
||||
.filter(r -> r.getId().toString().equals(replicableIdAsString)).findAny().orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public class Activator implements BundleActivator {
|
||||
public static final String PROPERTY_NAME_REPLICATE_MASTER_QUEUE_PORT = "replicate.master.queue.port";
|
||||
public static final String PROPERTY_NAME_REPLICATE_MASTER_EXCHANGE_NAME = "replicate.master.exchange.name";
|
||||
|
||||
private ReplicationInstancesManager<?> replicationInstancesManager;
|
||||
private ReplicationInstancesManager replicationInstancesManager;
|
||||
|
||||
private static BundleContext defaultContext;
|
||||
|
||||
@@ -110,15 +110,15 @@ public class Activator implements BundleActivator {
|
||||
} catch (NumberFormatException nfe) {
|
||||
logger.severe("Couldn't parse the replication port specification \""+exchangePortAsString+"\". Using default.");
|
||||
}
|
||||
replicationInstancesManager = new ReplicationInstancesManager<>();
|
||||
ReplicationService<?> serverReplicationMasterService = new ReplicationServiceImpl<>(
|
||||
exchangeName, exchangeHost, exchangePort, replicationInstancesManager);
|
||||
replicationInstancesManager = new ReplicationInstancesManager();
|
||||
ReplicationService serverReplicationMasterService = new ReplicationServiceImpl(
|
||||
exchangeName, exchangeHost, exchangePort, replicationInstancesManager, new OSGiReplicableTracker(bundleContext));
|
||||
bundleContext.registerService(ReplicationService.class, serverReplicationMasterService, null);
|
||||
logger.info("Registered replication service "+serverReplicationMasterService+" using exchange name "+exchangeName+" on host "+exchangeHost);
|
||||
checkIfAutomaticReplicationShouldStart(serverReplicationMasterService, exchangeName);
|
||||
}
|
||||
|
||||
private void checkIfAutomaticReplicationShouldStart(ReplicationService<?> serverReplicationMasterService, String masterExchangeName) {
|
||||
private void checkIfAutomaticReplicationShouldStart(ReplicationService serverReplicationMasterService, String masterExchangeName) {
|
||||
String replicateOnStart = System.getProperty(PROPERTY_NAME_REPLICATE_ON_START);
|
||||
if (Boolean.valueOf(replicateOnStart)) {
|
||||
logger.info("Configuration requested automatic replication. Starting it up...");
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.sap.sse.replication.impl;
|
||||
|
||||
import com.sap.sse.replication.OperationWithResult;
|
||||
import com.sap.sse.replication.Replicable;
|
||||
|
||||
public interface HasReplicable<S, O extends OperationWithResult<S, ?>> {
|
||||
Replicable<S, O> getReplicable();
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.sap.sse.replication.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.ServiceReference;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
||||
|
||||
import com.sap.sse.replication.Replicable;
|
||||
|
||||
/**
|
||||
* Uses an OSGi {@link ServiceTracker} to track and follow the set of {@link Replicable} objects registered with
|
||||
* the OSGi service registry under that interface and uses the information and the events from the tracker to
|
||||
* prepare the responses to the {@link #getReplicable(String)} and {@link #getReplicables()} method calls.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class OSGiReplicableTracker extends AbstractReplicablesProvider {
|
||||
private final ServiceTracker<Replicable<?, ?>, Replicable<?, ?>> serviceTracker;
|
||||
private final Map<String, ServiceReference<Replicable<?, ?>>> serviceReferenceByIdAsString;
|
||||
private final Map<ServiceReference<Replicable<?, ?>>, String> idAsStringByServiceReference;
|
||||
private final BundleContext bundleContext;
|
||||
|
||||
public OSGiReplicableTracker(final BundleContext bundleContext) {
|
||||
this.bundleContext = bundleContext;
|
||||
this.serviceReferenceByIdAsString = new HashMap<>();
|
||||
this.idAsStringByServiceReference = new HashMap<>();
|
||||
this.serviceTracker = new ServiceTracker<Replicable<?, ?>, Replicable<?, ?>>(
|
||||
bundleContext, Replicable.class.getName(), new ServiceTrackerCustomizer<Replicable<?, ?>, Replicable<?, ?>>() {
|
||||
@Override
|
||||
public Replicable<?, ?> addingService(ServiceReference<Replicable<?, ?>> reference) {
|
||||
final Replicable<?, ?> result = bundleContext.getService(reference);
|
||||
serviceReferenceByIdAsString.put(result.getId().toString(), reference);
|
||||
idAsStringByServiceReference.put(reference, result.getId().toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removedService(ServiceReference<Replicable<?, ?>> reference,
|
||||
Replicable<?, ?> service) {
|
||||
serviceReferenceByIdAsString.remove(idAsStringByServiceReference.get(reference));
|
||||
idAsStringByServiceReference.remove(reference);
|
||||
}
|
||||
|
||||
@Override public void modifiedService(ServiceReference<Replicable<?, ?>> reference, Replicable<?, ?> service) {}
|
||||
});
|
||||
serviceTracker.open();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Replicable<?, ?>> getReplicables() {
|
||||
return Arrays.asList(serviceTracker.getServices(new Replicable<?, ?>[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Replicable<?, ?> getReplicable(String replicableIdAsString) {
|
||||
return bundleContext.getService(serviceReferenceByIdAsString.get(replicableIdAsString));
|
||||
}
|
||||
}
|
||||
+13
-6
@@ -9,15 +9,22 @@ import com.sap.sse.common.IsManagedByCache;
|
||||
import com.sap.sse.replication.Replicable;
|
||||
|
||||
/**
|
||||
* During de-serialization, resolves objects managed by a {@link DomainFactory} using the domain factory instance passed
|
||||
* to the constructor. Additionally, class loading is managed using the current thread's context class loader which is
|
||||
* required in and works well for OSGi environments.
|
||||
* During de-serialization, resolves objects managed by a cache of type <C> using a cache of that type passed to the
|
||||
* constructor. Objects are identified as being managed by such a cache by means of the marker interface
|
||||
* {@link IsManagedByCache} that their classes then have to implement, telling how resolving against the cache
|
||||
* has to work for instances of that type.
|
||||
* <p>
|
||||
*
|
||||
* Create an instance of this class using
|
||||
* {@link DomainFactory#createObjectInputStreamResolvingAgainstThisFactory(InputStream)}.
|
||||
* Additionally, class loading is managed using the current thread's context class loader which is required in and works
|
||||
* well for OSGi environments.
|
||||
* <p>
|
||||
*
|
||||
* @param <C> the cache type used for resolving objects
|
||||
* Create an instance of this class by first creating a subclass in the bundle that has all the dependencies required to
|
||||
* resolve all the classes whose instances are to be read from the stream and then instantiating this subclass. Otherwise,
|
||||
* the stream won't be able to find the classes. To make this need clear, this class is declared <code>abstract</code>.
|
||||
*
|
||||
* @param <C>
|
||||
* the cache type used for resolving objects
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
package com.sap.sse.replication.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.sap.sse.replication.OperationWithResult;
|
||||
import com.sap.sse.replication.Replicable;
|
||||
|
||||
public interface ReplicableWithObjectInputStream<S, O extends OperationWithResult<S, ?>> extends Replicable<S, O> {
|
||||
/**
|
||||
* Produces an object input stream that can choose to resolve objects against a cache so that duplicate instances
|
||||
* are avoided.
|
||||
*/
|
||||
ObjectInputStream createObjectInputStreamResolvingAgainstCache(InputStream is) throws IOException;
|
||||
|
||||
/**
|
||||
* Implementation of {@link #initiallyFillFrom(InputStream)} which receives an {@link ObjectInputStream} instead of
|
||||
* an {@link InputStream}. The {@link ObjectInputStream} is expected to have been produced by
|
||||
* {@link #createObjectInputStreamResolvingAgainstCache(InputStream)}.
|
||||
*/
|
||||
void initiallyFillFromInternal(ObjectInputStream is) throws IOException, ClassNotFoundException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Implementation of {@link #serializeForInitialReplication(OutputStream)}, using an {@link ObjectOutputStream}.
|
||||
*/
|
||||
void serializeForInitialReplicationInternal(ObjectOutputStream objectOutputStream) throws IOException;
|
||||
|
||||
/**
|
||||
* Implementation of {@link #readOperation(InputStream)}, using the {@link ObjectInputStream} created by
|
||||
* {@link #createObjectInputStreamResolvingAgainstCache(InputStream)}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default O readOperationInternal(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
||||
return (O) ois.readObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void initiallyFillFrom(InputStream is) throws IOException, ClassNotFoundException, InterruptedException {
|
||||
initiallyFillFromInternal(createObjectInputStreamResolvingAgainstCache(is));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps <code>os</code> by an {@link ObjectOutputStream} and invokes
|
||||
* {@link #serializeForInitialReplicationInternal(ObjectOutputStream)}.
|
||||
*/
|
||||
@Override
|
||||
default void serializeForInitialReplication(OutputStream os) throws IOException {
|
||||
serializeForInitialReplicationInternal(new ObjectOutputStream(os));
|
||||
}
|
||||
|
||||
@Override
|
||||
default O readOperation(InputStream inputStream) throws IOException, ClassNotFoundException {
|
||||
return readOperationInternal(createObjectInputStreamResolvingAgainstCache(inputStream));
|
||||
}
|
||||
|
||||
}
|
||||
+8
-8
@@ -18,7 +18,7 @@ import com.sap.sse.replication.ReplicationMasterDescriptor;
|
||||
*
|
||||
* @param <S> the state to which replication operations are applied
|
||||
*/
|
||||
public class ReplicationInstancesManager<S> {
|
||||
public class ReplicationInstancesManager {
|
||||
|
||||
/**
|
||||
* The set of descriptors of all registered slaves. All broadcast operations will send the messages to all
|
||||
@@ -26,7 +26,7 @@ public class ReplicationInstancesManager<S> {
|
||||
*/
|
||||
private Set<ReplicaDescriptor> replicaDescriptors;
|
||||
|
||||
private Map<ReplicaDescriptor, Map<Class<? extends OperationWithResult<S, ?>>, Integer>> replicationCounts;
|
||||
private Map<ReplicaDescriptor, Map<Class<? extends OperationWithResult<?, ?>>, Integer>> replicationCounts;
|
||||
|
||||
/**
|
||||
* Used to calculate the average number of operations per message sent/received for each replica.
|
||||
@@ -54,7 +54,7 @@ public class ReplicationInstancesManager<S> {
|
||||
|
||||
public ReplicationInstancesManager() {
|
||||
replicaDescriptors = new HashSet<ReplicaDescriptor>();
|
||||
replicationCounts = new HashMap<ReplicaDescriptor, Map<Class<? extends OperationWithResult<S, ?>>,Integer>>();
|
||||
replicationCounts = new HashMap<ReplicaDescriptor, Map<Class<? extends OperationWithResult<?, ?>>,Integer>>();
|
||||
totalMessageCount = new HashMap<>();
|
||||
totalNumberOfOperations = new HashMap<>();
|
||||
totalQueueMessagesRawSizeInBytes = new HashMap<ReplicaDescriptor, Long>();
|
||||
@@ -80,7 +80,7 @@ public class ReplicationInstancesManager<S> {
|
||||
|
||||
public void registerReplica(ReplicaDescriptor replica) {
|
||||
replicaDescriptors.add(replica);
|
||||
replicationCounts.put(replica, new HashMap<Class<? extends OperationWithResult<S, ?>>, Integer>());
|
||||
replicationCounts.put(replica, new HashMap<Class<? extends OperationWithResult<?, ?>>, Integer>());
|
||||
}
|
||||
|
||||
public void unregisterReplica(ReplicaDescriptor replica) {
|
||||
@@ -96,15 +96,15 @@ public class ReplicationInstancesManager<S> {
|
||||
*/
|
||||
public void log(List<Class<?>> classes, long sizeOfQueueMessageInBytes) {
|
||||
for (ReplicaDescriptor replica : getReplicaDescriptors()) {
|
||||
Map<Class<? extends OperationWithResult<S, ?>>, Integer> counts = replicationCounts.get(replica);
|
||||
Map<Class<? extends OperationWithResult<?, ?>>, Integer> counts = replicationCounts.get(replica);
|
||||
if (counts == null) {
|
||||
counts = new HashMap<Class<? extends OperationWithResult<S, ?>>, Integer>();
|
||||
counts = new HashMap<Class<? extends OperationWithResult<?, ?>>, Integer>();
|
||||
replicationCounts.put(replica, counts);
|
||||
}
|
||||
for (Class<?> replicatedOperationClass : classes) {
|
||||
// safe because replicatedOperation is declared of type RacingEventserviceOperation<T>
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends OperationWithResult<S, ?>> operationClass = (Class<? extends OperationWithResult<S, ?>>) replicatedOperationClass;
|
||||
Class<? extends OperationWithResult<?, ?>> operationClass = (Class<? extends OperationWithResult<?, ?>>) replicatedOperationClass;
|
||||
Integer count = counts.get(operationClass);
|
||||
if (count == null) {
|
||||
count = 0;
|
||||
@@ -130,7 +130,7 @@ public class ReplicationInstancesManager<S> {
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Class<? extends OperationWithResult<S, ?>>, Integer> getStatistics(ReplicaDescriptor replica) {
|
||||
public Map<Class<? extends OperationWithResult<?, ?>>, Integer> getStatistics(ReplicaDescriptor replica) {
|
||||
return replicationCounts.get(replica);
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -13,6 +13,7 @@ import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
import com.rabbitmq.client.QueueingConsumer;
|
||||
import com.sap.sse.BuildVersion;
|
||||
import com.sap.sse.replication.Replicable;
|
||||
import com.sap.sse.replication.ReplicationMasterDescriptor;
|
||||
|
||||
public class ReplicationMasterDescriptorImpl implements ReplicationMasterDescriptor {
|
||||
@@ -72,7 +73,7 @@ public class ReplicationMasterDescriptorImpl implements ReplicationMasterDescrip
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getInitialLoadURL() throws MalformedURLException {
|
||||
public URL getInitialLoadURL(Iterable<Replicable<?, ?>> replicables) throws MalformedURLException {
|
||||
return new URL("http", getHostname(), servletPort, REPLICATION_SERVLET + "?" + ReplicationServlet.ACTION + "="
|
||||
+ ReplicationServlet.Action.INITIAL_LOAD.name());
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.sap.sse.replication.impl;
|
||||
|
||||
import com.sap.sse.replication.OperationExecutionListener;
|
||||
import com.sap.sse.replication.OperationWithResult;
|
||||
import com.sap.sse.replication.Replicable;
|
||||
|
||||
/**
|
||||
* Listens for operation execution notifications from a single {@link Replicable} object. It asks the
|
||||
* {@link ReplicationServiceImpl} it was passed at construction to
|
||||
* {@link ReplicationServiceImpl#broadcastOperation(OperationWithResult, Replicable<S, O>) broadcast} the operation to any interested
|
||||
* replica.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
* @param <S>
|
||||
*/
|
||||
public class ReplicationServiceExecutionListener<S> implements OperationExecutionListener<S> {
|
||||
private final ReplicationServiceImpl replicationService;
|
||||
private final Replicable<S, ?> replicable;
|
||||
|
||||
/**
|
||||
* {@link Replicable#addOperationExecutionListener(OperationExecutionListener) Adds} this new listener to the <code>replicable</code>
|
||||
*/
|
||||
protected ReplicationServiceExecutionListener(ReplicationServiceImpl replicationService, Replicable<S, ?> replicable) {
|
||||
super();
|
||||
this.replicationService = replicationService;
|
||||
this.replicable = replicable;
|
||||
this.replicable.addOperationExecutionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link #broadcastOperation(OperationWithResult, Replicable<S, O>) Broadcasts} the <code>operation</code> to all registered
|
||||
* replicas by publishing it to the fan-out exchange.
|
||||
*/
|
||||
@Override
|
||||
public <T> void executed(OperationWithResult<S, T> operation) {
|
||||
try {
|
||||
replicationService.broadcastOperation(operation, replicable);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void unsubscribe() {
|
||||
this.replicable.removeOperationExecutionListener(this);
|
||||
}
|
||||
}
|
||||
+129
-92
@@ -2,18 +2,20 @@ package com.sap.sse.replication.impl;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
@@ -30,37 +32,48 @@ import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
import com.rabbitmq.client.QueueingConsumer;
|
||||
import com.sap.sse.BuildVersion;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.replication.OperationExecutionListener;
|
||||
import com.sap.sse.replication.OperationWithResult;
|
||||
import com.sap.sse.replication.Replicable;
|
||||
import com.sap.sse.replication.ReplicablesProvider;
|
||||
import com.sap.sse.replication.ReplicablesProvider.ReplicableLifeCycleListener;
|
||||
import com.sap.sse.replication.ReplicationMasterDescriptor;
|
||||
import com.sap.sse.replication.ReplicationService;
|
||||
|
||||
/**
|
||||
* Can observe a {@link Replicable} for the operations it performs that require replication. Only observes as long as
|
||||
* there are replicas registered. If the last replica is de-registered, the service stops observing the
|
||||
* {@link Replicable}. Operations received that require replication are sent to the {@link Exchange} to which replica
|
||||
* queues can bind, using a {@link Replicator}. The exchange name and connectivity information for the message queuing
|
||||
* system is provided to this service during construction.
|
||||
* Manages a set of observers of {@link Replicable}, receiving notifications for the operations they perform that
|
||||
* require replication. This service provides the connectivity and central management including central keeping of
|
||||
* replication statistics for this server instance. Triggering the broadcast of an operation notified by a
|
||||
* {@link Replicable} is in the responsibility of each individual observer.
|
||||
* <p>
|
||||
*
|
||||
* This service object {@link Replicable#addOperationExecutionListener(OperationExecutionListener) registers} as
|
||||
* listener at the {@link Replicable} so it {@link #executed(OperationWithResult) receives} notifications about
|
||||
* operations executed by the {@link Replicable} that require replication if and only if there is at least one
|
||||
* replica registered.
|
||||
* The observers are registered only when there are replicas registered. If the last replica is de-registered, the
|
||||
* service stops observing the {@link Replicable}. Operations received that require replication are sent to the
|
||||
* {@link Exchange} to which replica queues can bind, using a {@link Replicator}. By prefixing each message with the
|
||||
* {@link Object#toString()} representation of the {@link Replicable}'s {@link Replicable#getId() ID} the receiver can
|
||||
* determine to which {@link Replicable} to forward the operation. As such, this service multiplexes the replication
|
||||
* channels for potentially many {@link Replicable}s living in this server instance.
|
||||
* <p>
|
||||
*
|
||||
* The exchange name and connectivity information for the message queuing system are provided to this service during
|
||||
* construction.
|
||||
* <p>
|
||||
*
|
||||
* This service object {@link Replicable#addOperationExecutionListener(OperationExecutionListener) registers} individual
|
||||
* listeners at the {@link Replicable}s so it {@link #executed(OperationWithResult) receives} notifications about
|
||||
* operations executed by the {@link Replicable} that require replication if and only if there is at least one replica
|
||||
* registered.
|
||||
*
|
||||
* @author Frank Mittag, Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class ReplicationServiceImpl<S, O extends OperationWithResult<S, ?>> implements ReplicationService<S>,
|
||||
OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
public class ReplicationServiceImpl implements ReplicationService {
|
||||
private static final Logger logger = Logger.getLogger(ReplicationServiceImpl.class.getName());
|
||||
|
||||
private final ReplicationInstancesManager<S> replicationInstancesManager;
|
||||
private final ReplicationInstancesManager replicationInstancesManager;
|
||||
|
||||
private final ServiceTracker<Replicable<S, O>, Replicable<S, O>> replicableTracker;
|
||||
|
||||
private final Replicable<S, O> localService;
|
||||
private final ReplicablesProvider replicablesProvider;
|
||||
|
||||
/**
|
||||
* <code>null</code>, if this instance is not currently replicating from some master; the master's descriptor otherwise
|
||||
@@ -102,7 +115,9 @@ OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
* For this instance running as a replica, the replicator receives messages from the master's queue and applies them
|
||||
* to the local replica.
|
||||
*/
|
||||
private Replicator<S, O> replicator;
|
||||
private Replicator replicator;
|
||||
|
||||
private final Set<ReplicationServiceExecutionListener<?>> executionListeners;
|
||||
|
||||
private Thread replicatorThread;
|
||||
|
||||
@@ -120,6 +135,17 @@ OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
* are replaced or cleared when the timer scoops up the currently buffered operations to send them out.
|
||||
*/
|
||||
private ByteArrayOutputStream outboundBuffer;
|
||||
|
||||
/**
|
||||
* The {@link #outboundBuffer} contains a message with serialized operations originating from a single
|
||||
* {@link Replicable} whose {@link Replicable#getId() ID} is written as its string value to the beginning
|
||||
* of the stream using {@link DataOutputStream#writeUTF(String)}. When an operation of a {@link Replicable}
|
||||
* with a different ID is to be {@link #broadcastOperation(OperationWithResult, Replicable) broadcast}, the
|
||||
* existing {@link #outboundBuffer} needs to be transmitted and a new one is started for the {@link Replicable}
|
||||
* now wanting to replicate an operation. Access is synchronized, as for {@link #outboundBuffer} using the
|
||||
* {@link #outboundBufferMonitor}.
|
||||
*/
|
||||
private String outboundBufferReplicableIdAsString;
|
||||
|
||||
/**
|
||||
* An object output stream that writes to {@link #outboundBuffer}. Operations are serialized into this stream until the timer
|
||||
@@ -173,51 +199,52 @@ OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
*/
|
||||
private long messageCount;
|
||||
|
||||
/**
|
||||
* @param exchangeName name of the fan-out exchange to which replication operations will be sent
|
||||
* @param exchangeHost name of the host under which the RabbitMQ server can be reached
|
||||
* @param exchangePort port of the RabbitMQ server, or 0 for default port
|
||||
*/
|
||||
public ReplicationServiceImpl(String exchangeName, String exchangeHost, int exchangePort, final ReplicationInstancesManager<S> replicationInstancesManager) throws IOException {
|
||||
this(exchangeName, exchangeHost, exchangePort, replicationInstancesManager, /* localService */ null, /* create ReplicableServiceTracker */ true);
|
||||
private class LifeCycleListener implements ReplicableLifeCycleListener {
|
||||
@Override
|
||||
public void replicableAdded(Replicable<?, ?> replicable) {
|
||||
addNewOperationExecutionListener(replicable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replicableRemoved(Replicable<?, ?> replicable) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
private ReplicationServiceImpl(String exchangeName, String exchangeHost,
|
||||
int exchangePort, final ReplicationInstancesManager<S> replicationInstancesManager,
|
||||
Replicable<S, O> localService, boolean createReplicableServiceTracker) throws IOException {
|
||||
/**
|
||||
* @param exchangeName
|
||||
* name of the fan-out exchange to which replication operations will be sent
|
||||
* @param exchangeHost
|
||||
* name of the host under which the RabbitMQ server can be reached
|
||||
* @param exchangePort
|
||||
* port of the RabbitMQ server, or 0 for default port
|
||||
* @param replicablesProvider
|
||||
* lets this service request the currently known {@link Replicable} objects to be observed for
|
||||
* replication; it also offers this service the possibility to register for life cycle events of those
|
||||
* {@link Replicable} objects so that this service can stop observing them for operations to be
|
||||
* replicated when they have been removed, or start observing new {@link Recpliable} objects that were
|
||||
* added.
|
||||
*/
|
||||
public ReplicationServiceImpl(String exchangeName, String exchangeHost,
|
||||
int exchangePort, final ReplicationInstancesManager replicationInstancesManager,
|
||||
ReplicablesProvider replicablesProvider) throws IOException {
|
||||
timer = new Timer("ReplicationServiceImpl timer for delayed task sending");
|
||||
executionListeners = new HashSet<>();
|
||||
this.replicationInstancesManager = replicationInstancesManager;
|
||||
replicaUUIDs = new HashMap<ReplicationMasterDescriptor, String>();
|
||||
if (createReplicableServiceTracker) {
|
||||
replicableTracker = getReplicableTracker();
|
||||
replicableTracker.open();
|
||||
} else {
|
||||
replicableTracker = null;
|
||||
}
|
||||
this.localService = localService;
|
||||
this.replicablesProvider = replicablesProvider;
|
||||
this.exchangeName = exchangeName;
|
||||
this.exchangeHost = exchangeHost;
|
||||
this.exchangePort = exchangePort;
|
||||
replicablesProvider.addReplicableLifeCycleListener(new LifeCycleListener());
|
||||
replicator = null;
|
||||
serverUUID = UUID.randomUUID();
|
||||
logger.info("Setting " + serverUUID.toString() + " as unique replication identifier.");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #ReplicationServiceImpl(String, ReplicationInstancesManager)}, only that instead of using an OSGi
|
||||
* service tracker to discover the {@link Replicable}, the service to replicate is "injected" here.
|
||||
*
|
||||
* @param exchangeName
|
||||
* the name of the exchange to which replicas can bind
|
||||
*/
|
||||
public ReplicationServiceImpl(String exchangeName, String exchangeHost,
|
||||
final ReplicationInstancesManager<S> replicationInstancesManager, Replicable<S, O> localService) throws IOException {
|
||||
this(exchangeName, exchangeHost, 0, replicationInstancesManager, localService, /* create ReplicableServiceTracker */ false);
|
||||
}
|
||||
|
||||
protected ServiceTracker<Replicable<S, O>, Replicable<S, O>> getReplicableTracker() {
|
||||
return new ServiceTracker<Replicable<S, O>, Replicable<S, O>>(
|
||||
protected ServiceTracker<Replicable<?, ?>, Replicable<?, ?>> getReplicableTracker() {
|
||||
return new ServiceTracker<Replicable<?, ?>, Replicable<?, ?>>(
|
||||
Activator.getDefaultContext(), Replicable.class.getName(), null);
|
||||
}
|
||||
|
||||
@@ -247,15 +274,8 @@ OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Replicable<S, O> getReplicable() {
|
||||
Replicable<S, O> result;
|
||||
if (localService != null) {
|
||||
result = localService;
|
||||
} else {
|
||||
result = replicableTracker.getService();
|
||||
}
|
||||
return result;
|
||||
private Iterable<Replicable<?, ?>> getReplicables() {
|
||||
return replicablesProvider.getReplicables();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -273,7 +293,14 @@ OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
}
|
||||
|
||||
private void addAsListenerToReplicables() {
|
||||
getReplicable().addOperationExecutionListener(this);
|
||||
for (Replicable<?, ?> replicable : getReplicables()) {
|
||||
addNewOperationExecutionListener(replicable);
|
||||
}
|
||||
}
|
||||
|
||||
private <S> void addNewOperationExecutionListener(Replicable<S, ?> replicable) {
|
||||
final ReplicationServiceExecutionListener<S> listener = new ReplicationServiceExecutionListener<S>(this, replicable);
|
||||
executionListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -292,29 +319,41 @@ OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
}
|
||||
|
||||
private void removeAsListenerFromReplicables() {
|
||||
getReplicable().removeOperationExecutionListener(this);
|
||||
for (ReplicationServiceExecutionListener<?> listener : executionListeners) {
|
||||
listener.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a single operation for broadcast. The operation is added to {@link #outboundBuffer}, and if not already scheduled,
|
||||
* a {@link #timer} is created and scheduled to send in {@link #TRANSMISSION_DELAY_MILLIS} milliseconds.
|
||||
* Schedules a single operation for broadcast. The operation is added to {@link #outboundBuffer}, and if not already
|
||||
* scheduled, a {@link #timer} is created and scheduled to send in {@link #TRANSMISSION_DELAY_MILLIS} milliseconds.
|
||||
*
|
||||
* @param replicable
|
||||
* the replicable by which the operation was executed that now will be broadcast to all replicas; the {@link Replicable#getId() ID}
|
||||
* of this replica in its string form will be used to prefix the operation
|
||||
*/
|
||||
private void broadcastOperation(OperationWithResult<S, ?> operation) throws IOException {
|
||||
<S, O extends OperationWithResult<S, ?>> void broadcastOperation(OperationWithResult<?, ?> operation, Replicable<S, O> replicable) throws IOException {
|
||||
// need to write the operations one by one, making sure the ObjectOutputStream always writes
|
||||
// identical objects again if required because they may have changed state in between
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(bos);
|
||||
oos.writeObject(operation);
|
||||
oos.close();
|
||||
final byte[] bytes = bos.toByteArray();
|
||||
final byte[] serializedOperation = bos.toByteArray();
|
||||
synchronized (outboundBufferMonitor) {
|
||||
final String replicaIdAsString = replicable.getId().toString();
|
||||
if (outboundBuffer != null && !Util.equalsWithNull(outboundBufferReplicableIdAsString, replicaIdAsString)) {
|
||||
flushBufferToRabbitMQ(); // operation from a replicable different from that for which operations are buffered so far --> flush
|
||||
} // still holding the monitor, so no other broadcast request from a different replicable can step in between
|
||||
if (outboundBuffer == null) {
|
||||
outboundBuffer = new ByteArrayOutputStream();
|
||||
outboundBufferReplicableIdAsString = replicaIdAsString;
|
||||
GZIPOutputStream zipper = new GZIPOutputStream(outboundBuffer);
|
||||
new DataOutputStream(zipper).writeUTF(replicaIdAsString);
|
||||
outboundObjectBuffer = new ObjectOutputStream(zipper);
|
||||
outboundBufferClasses = new ArrayList<>();
|
||||
}
|
||||
outboundObjectBuffer.writeObject(bytes);
|
||||
outboundObjectBuffer.writeObject(serializedOperation);
|
||||
outboundBufferClasses.add(operation.getClass());
|
||||
if (outboundBuffer.size() > TRIGGER_MESSAGE_SIZE_IN_BYTES) {
|
||||
logger.info("Triggering replication because buffer holds " + outboundBuffer.size()
|
||||
@@ -367,6 +406,7 @@ OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
classesOfOperationsToSend = outboundBufferClasses;
|
||||
doSend = true;
|
||||
outboundBuffer = null;
|
||||
outboundBufferReplicableIdAsString = null;
|
||||
outboundObjectBuffer = null;
|
||||
outboundBufferClasses = null;
|
||||
} else {
|
||||
@@ -407,6 +447,11 @@ OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
return replicatingFromMaster;
|
||||
}
|
||||
|
||||
/**
|
||||
* The peer for this method is
|
||||
* {@link ReplicationServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
|
||||
* which implements the initial load sending process.
|
||||
*/
|
||||
@Override
|
||||
public void startToReplicateFrom(ReplicationMasterDescriptor master) throws IOException, ClassNotFoundException, InterruptedException {
|
||||
logger.info("Starting to replicate from "+master);
|
||||
@@ -430,30 +475,35 @@ OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
throw ex;
|
||||
}
|
||||
logger.info("Connection to exchange successful.");
|
||||
URL initialLoadURL = master.getInitialLoadURL();
|
||||
final Iterable<Replicable<?, ?>> replicables = replicablesProvider.getReplicables();
|
||||
URL initialLoadURL = master.getInitialLoadURL(replicables);
|
||||
logger.info("Initial load URL is "+initialLoadURL);
|
||||
replicator = new Replicator<S, O>(master, this, /* startSuspended */ true, consumer);
|
||||
// start receiving messages already now, but start in suspended mode
|
||||
replicatorThread = new Thread(replicator, "Replicator receiving from "+master.getMessagingHostname()+"/"+master.getExchangeName());
|
||||
final Replicable<?, ?> replicable = getReplicable();
|
||||
// clear Replicable state here, before starting to receive and de-serialized operations which builds up new state, e.g., in competitor store
|
||||
replicable.clearReplicaState(); // see also bug 2437
|
||||
replicator = new Replicator(master, replicablesProvider, /* startSuspended */true, consumer);
|
||||
// clear Replicable state here, before starting to receive and de-serialized operations which builds up
|
||||
// new state, e.g., in competitor store
|
||||
for (Replicable<?, ?> r : getReplicables()) {
|
||||
r.clearReplicaState();
|
||||
}
|
||||
replicatorThread = new Thread(replicator, "Replicator receiving from " + master.getMessagingHostname() + "/"
|
||||
+ master.getExchangeName());
|
||||
replicatorThread.start();
|
||||
logger.info("Started replicator thread");
|
||||
InputStream is = initialLoadURL.openStream();
|
||||
final String queueName = new BufferedReader(new InputStreamReader(is)).readLine();
|
||||
RabbitInputStreamProvider rabbitInputStreamProvider = new RabbitInputStreamProvider(master.createChannel(),
|
||||
queueName);
|
||||
try {
|
||||
RabbitInputStreamProvider rabbitInputStreamProvider = new RabbitInputStreamProvider(master.createChannel(), queueName);
|
||||
ObjectInputStream ois = replicable.createObjectInputStreamResolvingAgainstCache(
|
||||
new GZIPInputStream(rabbitInputStreamProvider.getInputStream()));
|
||||
logger.info("Starting to receive initial load");
|
||||
replicable.initiallyFillFrom(ois);
|
||||
logger.info("Done receiving initial load");
|
||||
replicator.setSuspended(false); // apply queued operations
|
||||
for (Replicable<?, ?> replicable : getReplicables()) {
|
||||
logger.info("Starting to receive initial load");
|
||||
replicable.initiallyFillFrom(new GZIPInputStream(rabbitInputStreamProvider.getInputStream()));
|
||||
logger.info("Done receiving initial load");
|
||||
replicator.setSuspended(false); // apply queued operations
|
||||
}
|
||||
} finally {
|
||||
// delete initial load queue
|
||||
DeleteOk deleteOk = consumer.getChannel().queueDelete(queueName);
|
||||
logger.info("Deleted queue "+queueName+" used for initial load: "+deleteOk.toString());
|
||||
logger.info("Deleted queue " + queueName + " used for initial load: " + deleteOk.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,25 +548,12 @@ OperationExecutionListener<S>, HasReplicable<S, O> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link #broadcastOperation(OperationWithResult) Broadcasts} the <code>operation</code> to all registered
|
||||
* replicas by publishing it to the fan-out exchange.
|
||||
*/
|
||||
@Override
|
||||
public <T> void executed(OperationWithResult<S, T> operation) {
|
||||
try {
|
||||
broadcastOperation(operation);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerReplicaUuidForMaster(String uuid, ReplicationMasterDescriptor master) {
|
||||
replicaUUIDs.put(master, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Class<? extends OperationWithResult<S, ?>>, Integer> getStatistics(ReplicaDescriptor replicaDescriptor) {
|
||||
public Map<Class<? extends OperationWithResult<?, ?>>, Integer> getStatistics(ReplicaDescriptor replicaDescriptor) {
|
||||
return replicationInstancesManager.getStatistics(replicaDescriptor);
|
||||
}
|
||||
|
||||
|
||||
+20
-19
@@ -52,19 +52,20 @@ public class ReplicationServlet extends AbstractHttpServlet {
|
||||
*/
|
||||
private static final int INITIAL_LOAD_PACKAGE_SIZE = 1024*1024;
|
||||
|
||||
private ServiceTracker<ReplicationService<?>, ReplicationService<?>> replicationServiceTracker;
|
||||
public static final String REPLICA_IDS_AS_STRINGS_COMMA_SEPARATED = "replicaIdsAsStringsCommaSeparated";
|
||||
|
||||
private final ServiceTracker<ReplicationService, ReplicationService> replicationServiceTracker;
|
||||
|
||||
private ServiceTracker<Replicable<?, ?>, Replicable<?, ?>> replicableServiceTracker;
|
||||
private final OSGiReplicableTracker replicablesProvider;
|
||||
|
||||
public ReplicationServlet() throws Exception {
|
||||
BundleContext context = Activator.getDefaultContext();
|
||||
replicationServiceTracker = new ServiceTracker<ReplicationService<?>, ReplicationService<?>>(context, ReplicationService.class.getName(), null);
|
||||
replicablesProvider = new OSGiReplicableTracker(context);
|
||||
replicationServiceTracker = new ServiceTracker<ReplicationService, ReplicationService>(context, ReplicationService.class.getName(), null);
|
||||
replicationServiceTracker.open();
|
||||
replicableServiceTracker = new ServiceTracker<Replicable<?, ?>, Replicable<?, ?>>(context, Replicable.class.getName(), null);
|
||||
replicableServiceTracker.open();
|
||||
}
|
||||
|
||||
protected ReplicationService<?> getReplicationService() {
|
||||
protected ReplicationService getReplicationService() {
|
||||
return replicationServiceTracker.getService();
|
||||
}
|
||||
|
||||
@@ -87,6 +88,7 @@ public class ReplicationServlet extends AbstractHttpServlet {
|
||||
deregisterClientWithReplicationService(req, resp);
|
||||
break;
|
||||
case INITIAL_LOAD:
|
||||
String[] replicableIdsAsStrings = req.getParameter(REPLICA_IDS_AS_STRINGS_COMMA_SEPARATED).split(",");
|
||||
Channel channel = getReplicationService().createMasterChannel();
|
||||
RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel,
|
||||
/* queueName */ "initialLoad-for-"+req.getRemoteHost()+"@"+new Date()+"-"+UUID.randomUUID(),
|
||||
@@ -98,15 +100,18 @@ public class ReplicationServlet extends AbstractHttpServlet {
|
||||
ros, /* log every megabyte */1024l * 1024l, Level.INFO,
|
||||
"HTTP output for initial load for " + req.getRemoteHost());
|
||||
final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(countingOutputStream);
|
||||
ObjectOutputStream oos = new ObjectOutputStream(gzipOutputStream);
|
||||
try {
|
||||
getReplicable().serializeForInitialReplication(oos);
|
||||
gzipOutputStream.finish();
|
||||
} catch (Exception e) {
|
||||
logger.info("Error trying to serialize initial load for replication: "+e.getMessage());
|
||||
logger.log(Level.SEVERE, "doGet", e);
|
||||
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
e.printStackTrace(resp.getWriter());
|
||||
for (String replicableIdAsString : replicableIdsAsStrings) {
|
||||
Replicable<?, ?> replicable = replicablesProvider.getReplicable(replicableIdAsString);
|
||||
ObjectOutputStream oos = new ObjectOutputStream(gzipOutputStream);
|
||||
try {
|
||||
replicable.serializeForInitialReplication(oos);
|
||||
gzipOutputStream.finish();
|
||||
} catch (Exception e) {
|
||||
logger.info("Error trying to serialize initial load for replication: " + e.getMessage());
|
||||
logger.log(Level.SEVERE, "doGet", e);
|
||||
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
e.printStackTrace(resp.getWriter());
|
||||
}
|
||||
}
|
||||
countingOutputStream.close();
|
||||
break;
|
||||
@@ -116,10 +121,6 @@ public class ReplicationServlet extends AbstractHttpServlet {
|
||||
}
|
||||
}
|
||||
|
||||
private Replicable<?, ?> getReplicable() {
|
||||
return replicableServiceTracker.getService();
|
||||
}
|
||||
|
||||
private void deregisterClientWithReplicationService(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
ReplicaDescriptor replica = getReplicaDescriptor(req);
|
||||
getReplicationService().unregisterReplica(replica);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package com.sap.sse.replication.impl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -15,22 +18,35 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import com.rabbitmq.client.ConsumerCancelledException;
|
||||
import com.rabbitmq.client.QueueingConsumer;
|
||||
import com.rabbitmq.client.QueueingConsumer.Delivery;
|
||||
import com.rabbitmq.client.ShutdownSignalException;
|
||||
import com.sap.sse.common.Named;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.replication.OperationWithResult;
|
||||
import com.sap.sse.replication.Replicable;
|
||||
import com.sap.sse.replication.ReplicablesProvider;
|
||||
import com.sap.sse.replication.ReplicationMasterDescriptor;
|
||||
|
||||
/**
|
||||
* Receives {@link OperationWithResult} objects from a message queue and {@link Replicable#apply(OperationWithResult)
|
||||
* applies} them to the {@link Replicable} passed to this replicator at construction. When started in suspended mode,
|
||||
* messages received will be turned into {@link OperationWithResult} objects and then queued until
|
||||
* {@link #setSuspended(boolean) setSuspended(false)} is invoked which applies all queued operations before applying the
|
||||
* ones received later.
|
||||
* applies} them to the {@link Replicable} objects that can be found through the {@link ReplicablesProvider} passed to
|
||||
* this replicator at construction, based on the {@link Replicable#getId() replicable's ID} which is used in the stream
|
||||
* to prefix the operations.
|
||||
* <p>
|
||||
*
|
||||
* The corresponding writer for this protocol is implemented by
|
||||
* {@link ReplicationServiceImpl#broadcastOperation(OperationWithResult, Replicable)}.<p>
|
||||
*
|
||||
* When started in suspended mode, messages received will be turned into {@link OperationWithResult} objects and then
|
||||
* queued until {@link #setSuspended(boolean) setSuspended(false)} is invoked which applies all queued operations before
|
||||
* applying the ones received later.
|
||||
* <p>
|
||||
*
|
||||
* The receiver takes care of synchronizing receiving, suspending/resuming and queuing. Waiters are notified whenever
|
||||
@@ -39,15 +55,20 @@ import com.sap.sse.replication.ReplicationMasterDescriptor;
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class Replicator<S, O extends OperationWithResult<S, ?>> implements Runnable {
|
||||
public class Replicator implements Runnable {
|
||||
private final static Logger logger = Logger.getLogger(Replicator.class.getName());
|
||||
|
||||
private static final long CHECK_INTERVAL_MILLIS = 2000; // how long (milliseconds) to pause before checking connection again
|
||||
private static final int CHECK_COUNT = 150; // how long to check, value is CHECK_INTERVAL second steps
|
||||
|
||||
private final ReplicationMasterDescriptor master;
|
||||
private final HasReplicable<S, O> replicableProvider;
|
||||
private final List<OperationWithResult<S, ?>> queue;
|
||||
private final ReplicablesProvider replicableProvider;
|
||||
|
||||
/**
|
||||
* Keys are the {@link Replicable}s' IDs as string; values are the operation queues for the replicable identified by the
|
||||
* key.
|
||||
*/
|
||||
private final Map<String, List<Pair<String, OperationWithResult<?, ?>>>> queueByReplicableIdAsString;
|
||||
|
||||
private QueueingConsumer consumer;
|
||||
|
||||
@@ -90,8 +111,8 @@ public class Replicator<S, O extends OperationWithResult<S, ?>> implements Runna
|
||||
* @param consumer
|
||||
* the RabbitMQ consumer from which to load messages
|
||||
*/
|
||||
public Replicator(ReplicationMasterDescriptor master, HasReplicable<S, O> replicableProvider, boolean startSuspended, QueueingConsumer consumer) {
|
||||
this.queue = new ArrayList<OperationWithResult<S, ?>>();
|
||||
public Replicator(ReplicationMasterDescriptor master, ReplicablesProvider replicableProvider, boolean startSuspended, QueueingConsumer consumer) {
|
||||
this.queueByReplicableIdAsString = new HashMap<>();
|
||||
this.master = master;
|
||||
this.replicableProvider = replicableProvider;
|
||||
this.suspended = startSuspended;
|
||||
@@ -136,31 +157,37 @@ public class Replicator<S, O extends OperationWithResult<S, ?>> implements Runna
|
||||
}
|
||||
byte[] bytesFromMessage = delivery.getBody();
|
||||
checksPerformed = 0;
|
||||
// Set this object's class's class loader as context for de-serialization so that all exported classes
|
||||
// Set the replicable's class's class loader as context for deserialization so that all exported classes
|
||||
// of all required bundles/packages can be deserialized at least
|
||||
Replicable<S, O> replicable = replicableProvider.getReplicable();
|
||||
Thread.currentThread().setContextClassLoader(replicable.getClass().getClassLoader());
|
||||
ObjectInputStream ois = replicable
|
||||
.createObjectInputStreamResolvingAgainstCache(
|
||||
new GZIPInputStream(new ByteArrayInputStream(bytesFromMessage)));
|
||||
int operationsInMessage = 0;
|
||||
try {
|
||||
while (true) {
|
||||
byte[] serializedOperation = (byte[]) ois.readObject();
|
||||
ObjectInputStream operationOIS = replicable
|
||||
.createObjectInputStreamResolvingAgainstCache(new ByteArrayInputStream(serializedOperation));
|
||||
@SuppressWarnings("unchecked")
|
||||
OperationWithResult<S, ?> operation = (OperationWithResult<S, ?>) operationOIS.readObject();
|
||||
operationCount++;
|
||||
operationsInMessage++;
|
||||
if (operationCount % 10000l == 0) {
|
||||
logger.info("Received " + operationCount + " operations so far");
|
||||
final GZIPInputStream uncompressedInputStream = new GZIPInputStream(new ByteArrayInputStream(bytesFromMessage));
|
||||
String replicableIdAsString = new DataInputStream(uncompressedInputStream).readUTF();
|
||||
Replicable<?, ?> replicable = replicableProvider.getReplicable(replicableIdAsString);
|
||||
if (replicable != null) {
|
||||
Thread.currentThread().setContextClassLoader(replicable.getClass().getClassLoader());
|
||||
ObjectInputStream ois = new ObjectInputStream(uncompressedInputStream); // no special stream required; only reading a generic byte[]
|
||||
int operationsInMessage = 0;
|
||||
try {
|
||||
while (true) {
|
||||
byte[] serializedOperation = (byte[]) ois.readObject();
|
||||
readOperationAndApplyOrQueueIt(replicable, serializedOperation);
|
||||
operationCount++;
|
||||
operationsInMessage++;
|
||||
if (operationCount % 10000l == 0) {
|
||||
logger.info("Received " + operationCount + " operations so far");
|
||||
}
|
||||
}
|
||||
applyOrQueue(operation);
|
||||
} catch (EOFException eof) {
|
||||
logger.fine("Reached EOF on replication message after having read " + operationsInMessage
|
||||
+ " operations");
|
||||
// reached EOF; expected
|
||||
}
|
||||
} catch (EOFException eof) {
|
||||
logger.fine("Reached EOF on replication message after having read "+operationsInMessage+" operations");
|
||||
// reached EOF; expected
|
||||
} else {
|
||||
// otherwise, we don't know the replicable and simply drop the message with all the operations for the unknown recipient
|
||||
Stream<Named> sn = StreamSupport.stream(replicableProvider.getReplicables().spliterator(), /* parallel */ false)
|
||||
.map(r->(() -> r.getId().toString()));
|
||||
logger.warning("received replication message for replicable with ID "+replicableIdAsString+" which is unknnown by this replicator "+
|
||||
"which only knows "+
|
||||
Util.join(", ", sn::iterator));
|
||||
}
|
||||
} catch (ConsumerCancelledException cce) {
|
||||
logger.info("Consumer has been shut down properly.");
|
||||
@@ -217,27 +244,38 @@ public class Replicator<S, O extends OperationWithResult<S, ?>> implements Runna
|
||||
logger.info("Stopped replicator thread. This server will no longer receive events from a master.");
|
||||
}
|
||||
|
||||
public synchronized boolean isQueueEmpty() {
|
||||
return queue.isEmpty();
|
||||
private <S, O extends OperationWithResult<S, ?>> void readOperationAndApplyOrQueueIt(Replicable<S, O> replicable,
|
||||
byte[] serializedOperation) throws ClassNotFoundException, IOException {
|
||||
OperationWithResult<S, ?> operation = replicable.readOperation(new ByteArrayInputStream(serializedOperation));
|
||||
applyOrQueue(operation, replicable);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the replicator is currently {@link #suspended}, the <code>operation</code> is queued, otherwise immediately applied to
|
||||
* the receiving replica.
|
||||
* If the replicator is currently {@link #suspended}, the <code>operation</code> is queued, otherwise immediately
|
||||
* applied to the receiving replica.
|
||||
*
|
||||
* @param replicable
|
||||
* the replicable to which to apply or for which to queue the operation
|
||||
*/
|
||||
private synchronized void applyOrQueue(OperationWithResult<S, ?> operation) {
|
||||
private synchronized <S, O extends OperationWithResult<S, ?>> void applyOrQueue(OperationWithResult<S, ?> operation, Replicable<S, O> replicable) {
|
||||
if (suspended) {
|
||||
queue(operation);
|
||||
queue(operation, replicable);
|
||||
} else {
|
||||
apply(operation);
|
||||
apply(operation, replicable);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void apply(final OperationWithResult<S, ?> operation) {
|
||||
private synchronized <S, O extends OperationWithResult<S, ?>> void apply(final OperationWithResult<S, ?> operation, String replicableIdAsString) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Replicable<S, O> replicable = (Replicable<S, O>) replicableProvider.getReplicable(replicableIdAsString);
|
||||
apply(operation, replicable);
|
||||
}
|
||||
|
||||
private synchronized <S, O extends OperationWithResult<S, ?>> void apply(final OperationWithResult<S, ?> operation, Replicable<S, O> replicable) {
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
replicableProvider.getReplicable().apply(operation);
|
||||
replicable.apply(operation);
|
||||
}
|
||||
};
|
||||
if (operation.requiresSynchronousExecution()) {
|
||||
@@ -247,11 +285,12 @@ public class Replicator<S, O extends OperationWithResult<S, ?>> implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void queue(OperationWithResult<S, ?> operation) {
|
||||
private synchronized void queue(OperationWithResult<?, ?> operation, Replicable<?, ?> replicable) {
|
||||
List<Pair<String, OperationWithResult<?, ?>>> queue = queueByReplicableIdAsString.get(replicable.getId().toString());
|
||||
if (queue.isEmpty()) {
|
||||
notifyAll();
|
||||
}
|
||||
queue.add(operation);
|
||||
queue.add(new Pair<String, OperationWithResult<?, ?>>(replicable.getId().toString(), operation));
|
||||
assert !queue.isEmpty();
|
||||
}
|
||||
|
||||
@@ -259,22 +298,26 @@ public class Replicator<S, O extends OperationWithResult<S, ?>> implements Runna
|
||||
if (this.suspended != suspended) {
|
||||
this.suspended = suspended;
|
||||
if (!this.suspended) {
|
||||
applyQueue();
|
||||
applyQueues();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void applyQueue() {
|
||||
for (Iterator<OperationWithResult<S, ?>> i=queue.iterator(); i.hasNext(); ) {
|
||||
OperationWithResult<S, ?> operation = i.next();
|
||||
i.remove();
|
||||
try {
|
||||
apply(operation);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error applying queued, replicated operation "+operation+". Continuing with next queued operation.", e);
|
||||
private synchronized void applyQueues() {
|
||||
for (Entry<String, List<Pair<String, OperationWithResult<?, ?>>>> r : queueByReplicableIdAsString.entrySet()) {
|
||||
final List<Pair<String, OperationWithResult<?, ?>>> queue = r.getValue();
|
||||
for (Iterator<Pair<String, OperationWithResult<?, ?>>> i = queue.iterator(); i.hasNext();) {
|
||||
Pair<String, OperationWithResult<?, ?>> replicableIdAsStringAndOperation = i.next();
|
||||
i.remove();
|
||||
try {
|
||||
apply(replicableIdAsStringAndOperation.getB(), replicableIdAsStringAndOperation.getA());
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error applying queued, replicated operation "
|
||||
+ replicableIdAsStringAndOperation + ". Continuing with next queued operation.", e);
|
||||
}
|
||||
}
|
||||
assert queue.isEmpty();
|
||||
}
|
||||
assert queue.isEmpty();
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
@@ -285,7 +328,7 @@ public class Replicator<S, O extends OperationWithResult<S, ?>> implements Runna
|
||||
public synchronized void stop() {
|
||||
if (isSuspended()) {
|
||||
/* make sure to apply everything in queue before stopping this thread */
|
||||
applyQueue();
|
||||
applyQueues();
|
||||
}
|
||||
logger.info("Signaled Replicator thread to stop asap.");
|
||||
stopped = true;
|
||||
@@ -298,7 +341,8 @@ public class Replicator<S, O extends OperationWithResult<S, ?>> implements Runna
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Replicator for master "+master+", queue size: "+queue.size();
|
||||
long queueSize = queueByReplicableIdAsString.values().stream().mapToLong(l->l.size()).sum();
|
||||
return "Replicator for master "+master+", queue size: "+queueSize;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user