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}