/** * Copyright 2011-2017 Asakusa Framework Team. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.asakusafw.vocabulary.batch; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import com.asakusafw.vocabulary.flow.FlowDescription; /** * An abstract super class for describing the details of a batch workflow. * Subclasses must override {@link #describe()} method and build a workflow in the method, like as following: <pre><code> @Batch(name = "hoge") public class HogeBatch extends BatchDescription { @Override public void describe() { Work first = run(FirstFlow.class).soon(); Work second = run(SecondFlow.class).after(first); Work para = run(ParallelFlow.class).after(first); Work join = run(JoinFlow.class).after(second, para); ... } } </code></pre> * In the above example, only {@code FirstFlow} will be started first. And then, after the {@code FirstFlow} was * completed, {@code SecondFlow} and {@code ParallelFlow} will be concurrently processed. Finally, * {@code JoinFlow} was processed after the all other jobflows were completed. */ public abstract class BatchDescription { static final Work[] NOTHING = new Work[0]; private final Map<String, Work> works = new LinkedHashMap<>(); private DependencyBuilder adding; private final AtomicBoolean described = new AtomicBoolean(false); /** * Analyzes batch DSL using {@link #describe() batch description method}. * Application developers should not invoke this method directly. */ public final void start() { if (described.compareAndSet(false, true) == false) { return; } describe(); checkFlushed(); } /** * Describes workflow structure. * Subclasses must override this method and build a workflow using Asakusa batch DSL. */ protected abstract void describe(); /** * Start registering a new <em>script job</em> to this batch. * @param scriptDefinition the script definition path * @return a builder for specifying dependencies of the adding job * @throws IllegalArgumentException if the script definition is something wrong * @throws IllegalStateException if another job is building dependencies * @deprecated does not supported */ @Deprecated protected DependencyBuilder run(String scriptDefinition) { throw new UnsupportedOperationException(); } /** * Start registering a new <em>jobflow</em> to this batch. * Clients must tell the dependencies of the target jobflow to the resulting builder of this method, by using * either methods. * <ul> * <li> {@link DependencyBuilder#soon() run(...).soon()} </li> * <li> {@link DependencyBuilder#after(Work, Work...) run(...).after(...)} </li> * </ul> * The both above methods will return a {@link Work} object which represents the registered jobflow. * And then the latter method accepts {@link Work} objects as its dependencies. * @param jobflow the target jobflow class * @return a builder for specifying dependencies of the adding job * @throws IllegalArgumentException if the jobflow class is something wrong * @throws IllegalStateException if another job is building dependencies */ protected DependencyBuilder run(Class<? extends FlowDescription> jobflow) { if (jobflow == null) { throw new IllegalArgumentException("jobFlow must not be null"); //$NON-NLS-1$ } return run0(new JobFlowWorkDescription(jobflow)); } private DependencyBuilder run0(WorkDescription description) { assert description != null; checkFlushed(); DependencyBuilder builder = new DependencyBuilder(description); adding = builder; return builder; } private void checkFlushed() { if (adding != null) { throw new IllegalStateException(MessageFormat.format( Messages.getString("BatchDescription.errorIncomplete"), //$NON-NLS-1$ adding.description)); } } /** * Returns a collection of Unit-of-Works which is represented in this batch. * @return a collection of Unit-of-Works of this batch */ public Collection<Work> getWorks() { return new ArrayList<>(works.values()); } Work register(Work work) { assert work != null; String name = work.getDescription().getName(); if (works.containsKey(name)) { throw new IllegalStateException(MessageFormat.format( Messages.getString("BatchDescription.errorDuplicateDescription"), //$NON-NLS-1$ name, work.getDescription(), works.get(name).getDescription())); } works.put(name, work); adding = null; return work; } @Override public String toString() { return MessageFormat.format( "{0}'{'works={1}'}'", //$NON-NLS-1$ getClass().getName(), works); } /** * A builder for building batch by specifying Unit-of-Works and their dependencies. */ protected class DependencyBuilder { final WorkDescription description; DependencyBuilder(WorkDescription description) { assert description != null; this.description = description; } /** * Completes registering job without any dependencies. * @return the registered job */ public Work soon() { return register(new Work(BatchDescription.this, description, Collections.emptyList())); } /** * Completes registering job with dependencies. * The registered job will be executed after the precedent jobs were (successfully) completed. * @param dependency the precedent job * @param rest the other precedent job * @return the registered job * @throws IllegalArgumentException if some parameters are {@code null} */ public Work after(Work dependency, Work... rest) { if (dependency == null) { throw new IllegalArgumentException("dependency must not be null"); //$NON-NLS-1$ } if (rest == null) { throw new IllegalArgumentException("rest must not be null"); //$NON-NLS-1$ } List<Work> dependencies = new ArrayList<>(); dependencies.add(dependency); Collections.addAll(dependencies, rest); for (Work p : dependencies) { if (dependency.getDeclaring() != BatchDescription.this) { throw new IllegalArgumentException(MessageFormat.format( Messages.getString("BatchDescription.errorInvalidDependency"), //$NON-NLS-1$ p)); } } return register(new Work(BatchDescription.this, description, dependencies)); } } }