001// Generated by delombok at Mon Oct 12 22:59:35 BST 2020 002/* 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 */ 020package org.apache.isis.extensions.commandreplay.secondary.jobcallables; 021 022import java.util.List; 023import java.util.Optional; 024import java.util.concurrent.Callable; 025import java.util.stream.Collectors; 026import javax.inject.Inject; 027import org.apache.isis.applib.services.command.CommandExecutorService; 028import org.apache.isis.applib.services.xactn.TransactionService; 029import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo; 030import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdoRepository; 031import org.apache.isis.extensions.commandlog.impl.jdo.ReplayState; 032import org.apache.isis.extensions.commandreplay.secondary.SecondaryStatus; 033import org.apache.isis.extensions.commandreplay.secondary.StatusException; 034import org.apache.isis.extensions.commandreplay.secondary.analysis.CommandReplayAnalysisService; 035import org.apache.isis.extensions.commandreplay.secondary.fetch.CommandFetcher; 036import org.apache.isis.extensions.commandreplay.secondary.spi.ReplayCommandExecutionController; 037import org.apache.isis.schema.cmd.v2.CommandDto; 038 039/** 040 * Encodes the algorithm for fetching commands from the primary, and 041 042 * replaying on the secondary. 043 044 * 045 046 * <p> 047 048 * This class is instantiated each time the Quartz job 049 050 * (<code>{@link org.apache.isis.extensions.commandreplay.secondary.job.ReplicateAndReplayJob}</code>) 051 052 * files. 053 054 * </p> 055 */ 056public class ReplicateAndRunCommands implements Callable<SecondaryStatus> { 057 @java.lang.SuppressWarnings("all") 058 private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(ReplicateAndRunCommands.class); 059 @Inject 060 CommandExecutorService commandExecutorService; 061 @Inject 062 TransactionService transactionService; 063 @Inject 064 CommandFetcher commandFetcher; 065 @Inject 066 CommandJdoRepository commandJdoRepository; 067 @Inject 068 CommandReplayAnalysisService analysisService; 069 @Inject 070 Optional<ReplayCommandExecutionController> controller; 071 072 @Override 073 public SecondaryStatus call() { 074 try { 075 doCall(); 076 return SecondaryStatus.OK; 077 } catch (StatusException e) { 078 return e.secondaryStatus; 079 } 080 } 081 082 private void doCall() throws StatusException { 083 if (!isRunning()) { 084 log.debug("ReplicateAndRunCommands is paused"); 085 return; 086 } 087 List<CommandJdo> commandsToReplay; 088 while (isRunning()) { 089 // is there a pending command already? 090 // (we fetch several at a time, so we may not have processed them all yet) 091 commandsToReplay = commandJdoRepository.findNotYetReplayed(); 092 if (commandsToReplay.isEmpty()) { 093 // look for previously replayed on secondary 094 CommandJdo hwm = commandJdoRepository.findMostRecentReplayed().orElse(null); 095 if (hwm != null) { 096 // give up if there was a failure; admin will need to fix issue and retry 097 if (hwm.getReplayState() != null && hwm.getReplayState().isFailed()) { 098 log.info("Command {} hit replay error", hwm.getUniqueId()); 099 return; 100 } 101 } else { 102 // after a DB restore from primary to secondary, there won't be 103 // any that have been replayed. So instead we simply use 104 // latest completed (on primary) as the HWM. 105 hwm = commandJdoRepository.findMostRecentCompleted().orElse(null); 106 } 107 // fetch next command(s) from primary (if any) 108 final java.util.List<org.apache.isis.schema.cmd.v2.CommandDto> commandDtos = commandFetcher.fetchCommand(hwm); 109 commandsToReplay = commandDtos.stream().map(dto -> transactionService.executeWithinTransaction(() -> commandJdoRepository.saveForReplay(dto))).collect(Collectors.toList()); 110 if (commandsToReplay.isEmpty()) { 111 return; // nothing more to do for now. 112 } 113 } 114 replay(commandsToReplay); 115 } 116 } 117 118 /** 119 * @param commandsToReplay 120 121 * @return - whether there was a command to process (and so continue) 122 */ 123 private void replay(List<CommandJdo> commandsToReplay) { 124 commandsToReplay.forEach(commandJdo -> { 125 log.info("replaying {}", commandJdo.getUniqueId()); 126 // 127 // run command 128 // 129 final org.apache.isis.extensions.commandlog.impl.jdo.ReplayState replayState = executeCommandInTranAndAnalyse(commandJdo); 130 if (replayState.isFailed()) { 131 // will effectively block the running of any further commands 132 // until the issue is fixed. 133 return; 134 } 135 // 136 // find child commands, and run them 137 // 138 final org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo parent = commandJdo; 139 final java.util.List<org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo> childCommands = transactionService.executeWithinTransaction(() -> commandJdoRepository.findByParent(parent)); 140 for (final org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo childCommand : childCommands) { 141 final org.apache.isis.extensions.commandlog.impl.jdo.ReplayState childReplayState = executeCommandInTranAndAnalyse(childCommand); 142 if (childReplayState.isFailed()) { 143 // give up 144 return; 145 } 146 } 147 }); 148 } 149 150 private ReplayState executeCommandInTranAndAnalyse(final CommandJdo commandJdo) { 151 transactionService.executeWithinTransaction(() -> { 152 commandExecutorService.executeCommand(CommandExecutorService.SudoPolicy.SWITCH, commandJdo.getCommandDto(), commandJdo.outcomeHandler()); 153 }); 154 transactionService.executeWithinTransaction(() -> { 155 analysisService.analyse(commandJdo); 156 }); 157 return commandJdo.getReplayState(); 158 } 159 160 private boolean isRunning() { 161 return 162 // if no controller implementation provided, then just continue 163 controller.map(control -> transactionService.executeWithinTransaction(control::getState)).map(state -> state == ReplayCommandExecutionController.State.RUNNING).orElse(true); 164 } 165}