Skip to content
This repository has been archived by the owner on Jan 28, 2021. It is now read-only.

Commit

Permalink
#69 - uses ticking clock to run commands seemingly at the time that t…
Browse files Browse the repository at this point in the history
…hey occurred on master. Also...

also:
- CommandJdos when saved for replay use the timestamp from the DTO, rather than the startedAt.
- adds new TickingClockService (similar to SudoService, but for time rather than user)
- removes logic to populate userData in CommandJdo#asDto, this is now done in Isis' ContentMappingServiceForCommandDto.
  • Loading branch information
danhaywood committed Jan 31, 2018
1 parent c8306c4 commit 85826da
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 92 deletions.
Expand Up @@ -5,7 +5,6 @@

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -18,15 +17,12 @@ public class RunBackgroundCommandsJob implements Job {

private static final Logger LOG = LoggerFactory.getLogger(RunBackgroundCommandsJob.class);

public void execute(final JobExecutionContext context) throws JobExecutionException {

final BackgroundCommandExecutionFromBackgroundCommandServiceJdo exec = new BackgroundCommandExecutionFromBackgroundCommandServiceJdo();
public void execute(final JobExecutionContext context) {

final AuthenticationSession authSession = newAuthSession(context);

LOG.debug("Running background commands");
exec.execute(authSession, null);

new BackgroundCommandExecutionFromBackgroundCommandServiceJdo().execute(authSession, null);
}

protected String getKey(JobExecutionContext context, String key) {
Expand Down
Expand Up @@ -50,9 +50,6 @@
import org.apache.isis.objectstore.jdo.applib.service.JdoColumnLength;
import org.apache.isis.objectstore.jdo.applib.service.Util;
import org.apache.isis.schema.cmd.v1.CommandDto;
import org.apache.isis.schema.cmd.v1.MapDto;
import org.apache.isis.schema.common.v1.PeriodDto;
import org.apache.isis.schema.utils.jaxbadapters.JavaSqlTimestampXmlGregorianCalendarAdapter;

import org.isisaddons.module.command.CommandModule;

Expand Down Expand Up @@ -263,11 +260,6 @@ public class CommandJdo extends DomainChangeJdoAbstract implements Command3, Has
@SuppressWarnings("unused")
private static final Logger LOG = LoggerFactory.getLogger(CommandJdo.class);

static final String DTO_USERDATA_KEY_TARGET_CLASS = "targetClass";
static final String DTO_USERDATA_KEY_TARGET_ACTION = "targetAction";
static final String DTO_USERDATA_KEY_ARGUMENTS = "arguments";
static final String DTO_USERDATA_KEY_RETURN_VALUE = "returnValue";

//region > domain event superclasses
public static abstract class PropertyDomainEvent<T> extends CommandModule.PropertyDomainEvent<CommandJdo, T> { }

Expand Down Expand Up @@ -582,48 +574,8 @@ private CommandDto buildCommandDto() {
if(isLegacyMemento()) {
return null;
}
final CommandDto commandDto = jaxbService.fromXml(CommandDto.class, getMemento());

// for some reason this isn't being persisted initially, so patch it in. TODO: should fix this
commandDto.setUser(getUser());

putUserData(commandDto, DTO_USERDATA_KEY_TARGET_CLASS, getTargetClass());
putUserData(commandDto, DTO_USERDATA_KEY_TARGET_ACTION, getTargetAction());
putUserData(commandDto, DTO_USERDATA_KEY_ARGUMENTS, getArguments());
putUserData(commandDto, DTO_USERDATA_KEY_RETURN_VALUE, getReturnValue());
PeriodDto timings = commandDto.getTimings();
if(timings == null) {
timings = new PeriodDto();
commandDto.setTimings(timings);
}
timings.setStartedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(getStartedAt()));
timings.setCompletedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(getCompletedAt()));

return commandDto;
}

private static void putUserData(final CommandDto commandDto, final String key, final String value) {
if(value == null) {
return;
}
final MapDto userData = userDataFor(commandDto);
final MapDto.Entry entry = new MapDto.Entry();
entry.setKey(key);
entry.setValue(value);
userData.getEntry().add(entry);
}

private static MapDto userDataFor(final CommandDto commandDto) {
MapDto userData = commandDto.getUserData();
if(userData == null) {
userData = new MapDto();
commandDto.setUserData(userData);
}
return userData;
}

private String getReturnValue() {
return getResultStr();
return jaxbService.fromXml(CommandDto.class, getMemento());
}

//endregion
Expand Down
Expand Up @@ -3,10 +3,8 @@
import java.sql.Timestamp;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.UUID;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

Expand All @@ -24,14 +22,14 @@
import org.apache.isis.applib.services.bookmark.Bookmark;
import org.apache.isis.applib.services.command.Command;
import org.apache.isis.applib.services.command.CommandContext;
import org.apache.isis.applib.services.command.CommandWithDto;
import org.apache.isis.applib.services.jdosupport.IsisJdoSupport;
import org.apache.isis.applib.services.repository.RepositoryService;
import org.apache.isis.objectstore.jdo.applib.service.JdoColumnLength;
import org.apache.isis.schema.cmd.v1.CommandDto;
import org.apache.isis.schema.cmd.v1.CommandsDto;
import org.apache.isis.schema.cmd.v1.MapDto;
import org.apache.isis.schema.common.v1.OidDto;
import org.apache.isis.schema.common.v1.PeriodDto;
import org.apache.isis.schema.utils.CommandDtoUtils;
import org.apache.isis.schema.utils.jaxbadapters.JavaSqlTimestampXmlGregorianCalendarAdapter;

Expand Down Expand Up @@ -389,40 +387,22 @@ public void saveForReplay(final CommandDto dto) {


final MapDto userData = dto.getUserData();
final PeriodDto timings = dto.getTimings();
if (userData == null || timings == null) {
if (userData == null ) {
throw new IllegalStateException(String.format(
"Can only persist DTOs with additional timings and userData; got: \n%s",
"Can only persist DTOs with additional userData; got: \n%s",
CommandDtoUtils.toXml(dto)));
}

final Map<String, String> userDataMap =
userData.getEntry().stream()
.collect(Collectors.toMap(MapDto.Entry::getKey, MapDto.Entry::getValue));

final String targetClass = userDataMap.get(CommandJdo.DTO_USERDATA_KEY_TARGET_CLASS);
final String targetAction = userDataMap.get(CommandJdo.DTO_USERDATA_KEY_TARGET_ACTION);
final String arguments = userDataMap.get(CommandJdo.DTO_USERDATA_KEY_ARGUMENTS);
final Timestamp startedAt = JavaSqlTimestampXmlGregorianCalendarAdapter.parse(timings.getStartedAt());

final CommandJdo commandJdo = repositoryService.instantiate(CommandJdo.class);

final UUID transactionId = UUID.fromString(dto.getTransactionId());
commandJdo.setTransactionId(transactionId);

final String user = dto.getUser();
commandJdo.setUser(user);

// use the startedAt time on the master as the timestamp for this command on the slave,
// to (try to) ensure that commands are executed in the same order.
commandJdo.setTimestamp(startedAt);

commandJdo.setTransactionId(UUID.fromString(dto.getTransactionId()));
commandJdo.setTimestamp(JavaSqlTimestampXmlGregorianCalendarAdapter.parse(dto.getTimestamp()));
commandJdo.setUser(dto.getUser());
commandJdo.setExecuteIn(org.apache.isis.applib.annotation.Command.ExecuteIn.REPLAYABLE);

commandJdo.setTargetClass(targetClass);
commandJdo.setTargetAction(targetAction);

commandJdo.setArguments(arguments);
commandJdo.setTargetClass(CommandDtoUtils.getUserData(dto, CommandWithDto.USERDATA_KEY_TARGET_CLASS));
commandJdo.setTargetAction(CommandDtoUtils.getUserData(dto, CommandWithDto.USERDATA_KEY_TARGET_ACTION));
commandJdo.setArguments(CommandDtoUtils.getUserData(dto, CommandWithDto.USERDATA_KEY_ARGUMENTS));

commandJdo.setPersistHint(true);

Expand Down
Expand Up @@ -4,10 +4,12 @@

import org.apache.isis.applib.services.command.Command;
import org.apache.isis.core.runtime.services.background.BackgroundCommandExecution;
import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;

import org.isisaddons.module.command.dom.ReplayableCommandServiceJdoRepository;

public class ReplayableCommandExecutionFromReplayableCommandServiceJdo extends BackgroundCommandExecution {
public class ReplayableCommandExecutionFromReplayableCommandServiceJdo
extends BackgroundCommandExecution {

public ReplayableCommandExecutionFromReplayableCommandServiceJdo() {
super(OnExceptionPolicy.QUIT, SudoPolicy.SWITCH);
Expand All @@ -18,6 +20,17 @@ protected List<? extends Command> findBackgroundCommandsToExecute() {
return repository.findReplayableCommandsNotYetStartedUnlessBlocked();
}

protected Boolean executeCommand(
final IsisTransactionManager transactionManager,
final Command command) {

return tickingClockService.at(command.getTimestamp(),
() -> super.doExecuteCommand(transactionManager, command));
}

@javax.inject.Inject
TickingClockService tickingClockService;

@javax.inject.Inject
ReplayableCommandServiceJdoRepository repository;
}
Expand Up @@ -5,7 +5,6 @@

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -18,15 +17,12 @@ public class RunReplayableCommandsJob implements Job {

private static final Logger LOG = LoggerFactory.getLogger(RunBackgroundCommandsJob.class);

public void execute(final JobExecutionContext context) throws JobExecutionException {

final ReplayableCommandExecutionFromReplayableCommandServiceJdo exec = new ReplayableCommandExecutionFromReplayableCommandServiceJdo();
public void execute(final JobExecutionContext context) {

final AuthenticationSession authSession = newAuthSession(context);

LOG.debug("Running replicate commands");
exec.execute(authSession, null);

LOG.debug("Running replayable commands");
new ReplayableCommandExecutionFromReplayableCommandServiceJdo().execute(authSession, null);
}

protected String getKey(JobExecutionContext context, String key) {
Expand Down
@@ -0,0 +1,65 @@
package org.isisaddons.module.command.replay.impl;

import java.sql.Timestamp;
import java.util.concurrent.Callable;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.apache.isis.applib.ApplicationException;
import org.apache.isis.applib.annotation.DomainService;
import org.apache.isis.applib.annotation.NatureOfService;
import org.apache.isis.applib.annotation.Programmatic;
import org.apache.isis.applib.fixtures.TickingFixtureClock;
import org.apache.isis.core.metamodel.services.configinternal.ConfigurationServiceInternal;

/**
* If configured as the slave, then sets up to use {@link TickingFixtureClock}
* so that time can be changed dynamically when running.
*/
@DomainService(nature = NatureOfService.DOMAIN, menuOrder = "1")
public class TickingClockService {

@PostConstruct
public void init() {
if( notDefined(Constants.MASTER_BASE_URL_ISIS_KEY) ||
notDefined(Constants.MASTER_USER_ISIS_KEY) ||
notDefined(Constants.MASTER_PASSWORD_ISIS_KEY)) {
return;
}
TickingFixtureClock.replaceExisting();
}

private boolean notDefined(final String key) {
return configurationServiceInternal.getProperty(key) == null;
}

@Programmatic
public void at(Timestamp timestamp, Runnable runnable) {
final TickingFixtureClock instance = (TickingFixtureClock) TickingFixtureClock.getInstance();
final Timestamp previous = TickingFixtureClock.getTimeAsJavaSqlTimestamp();
try {
instance.setTime(timestamp);
runnable.run();
} finally {
instance.setTime(previous);
}
}

@Programmatic
public <T> T at(Timestamp timestamp, Callable<T> callable) {
final TickingFixtureClock instance = (TickingFixtureClock) TickingFixtureClock.getInstance();
final Timestamp previous = TickingFixtureClock.getTimeAsJavaSqlTimestamp();
try {
instance.setTime(timestamp);
return callable.call();
} catch (Exception e) {
throw new ApplicationException(e);
} finally {
instance.setTime(previous);
}
}

@Inject
ConfigurationServiceInternal configurationServiceInternal;
}

0 comments on commit 85826da

Please sign in to comment.