/*
* Copyright (c) 2015 Red Hat, Inc. and/or its affiliates.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cheng Fang - Initial API and implementation
*/
package org.jberet.job.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.batch.operations.BatchRuntimeException;
import org.jberet._private.BatchMessages;
/**
* Builder class for building a single {@linkplain Job job}. After the job is built, the same {@code JobBuilder} instance
* should not be reused to build another job.
* <p/>
* Unlike XML JSL, jobs built programmatically with this builder and other related builder classes do not support
* JSL inheritance, nor artifacts written in scripting languages.
* <p/>
* This class does not support multi-threaded access or modification. Usage example,
* <p/>
* <pre>
* Job job = new JobBuilder(jobName)
* .restartable(false)
* .property("jobk1", "J")
* .property("jobk2", "J")
* .listener("jobListener1", new String[]{"jobListenerk1", "#{jobParameters['jobListenerPropVal']}"},
* new String[]{"jobListenerk2", "#{jobParameters['jobListenerPropVal']}"})
*
* .step(new StepBuilder(stepName)
* .properties(new String[]{"stepk1", "S"}, new String[]{"stepk2", "S"})
* .batchlet(batchlet1Name, new String[]{"batchletk1", "B"}, new String[]{"batchletk2", "B"})
* .listener("stepListener1", stepListenerProps)
* .stopOn("STOP").restartFrom(stepName).exitStatus()
* .endOn("END").exitStatus("new status for end")
* .failOn("FAIL").exitStatus()
* .nextOn("*").to(step2Name)
* .build())
*
* .step(new StepBuilder(step2Name)
* .batchlet(batchlet1Name)
* .build())
*
* .build();
* </pre>
*
* @see StepBuilder
* @see FlowBuilder
* @see SplitBuilder
* @see DecisionBuilder
* @since 1.2.0
*/
public final class JobBuilder extends AbstractPropertiesBuilder<JobBuilder> {
private final String id;
private String restartable;
private Listeners listeners;
private final List<JobElement> jobElements = new ArrayList<JobElement>();
/**
* stores ids for all job elements: job, step, flow, split and decision. These ids should be checked for uniqueness.
*/
final Set<String> ids = new HashSet<String>();
/**
* Constructs a {@code JobBuilder} for building the job with the specified {@code id}.
*
* @param id the job id, corresponding to the id attribute of jsl:Job element in XML
*/
public JobBuilder(final String id) {
this.id = id;
}
/**
* Sets the {@code restartable} attribute value on the job. This method may be invoked with 0 or 1 boolean parameter.
* {@code restartable()} is equivalent to {@code restartable(true)}.
*
* @param b optional restartable value (true or false)
* @return this {@code JobBuilder}
*/
public JobBuilder restartable(final boolean... b) {
if (b.length == 0) {
this.restartable = String.valueOf(true);
} else {
this.restartable = String.valueOf(b[0]);
}
return this;
}
/**
* Adds a job listener to the job. The listener may be added with 0 or more listener properties. Each listener
* property is represented by a 2-element string array, whose 1st element is the property key, and 2nd element is
* the property value. For example,
* <p/>
* <pre>
* listener("listener1");
* listener1("listener2", new String[]{"key1", "value1"});
* listener1("listener3", new String[]{"key1", "value1"}, new String[]{"key2", "value2"});
* listener1("listener4", new String[]{"jobListenerk1", "#{jobParameters['jobListenerPropVal']}"}
* </pre>
*
* @param listenerRef job listener name
* @param pairsOfKeyValue optional listener properties in the form of a series of 2-element string arrays
* @return this {@code JobBuilder}
*/
public JobBuilder listener(final String listenerRef, final String[]... pairsOfKeyValue) {
if (listeners == null) {
listeners = new Listeners();
}
listeners.getListeners().add(createRefArtifactWithProperties(listenerRef, null, pairsOfKeyValue));
return this;
}
/**
* Adds a job listener to the job, with listener properties.
*
* @param listenerRef job listener name
* @param props job listener properties, null means no properties
* @return this {@code JobBuilder}
*/
public JobBuilder listener(final String listenerRef, final java.util.Properties props) {
if (listeners == null) {
listeners = new Listeners();
}
listeners.getListeners().add(createRefArtifactWithProperties(listenerRef, props));
return this;
}
/**
* Adds a {@linkplain Step step} to the job. The step is typically built with {@link StepBuilder}.
*
* @param step a pre-built step
* @return this {@code JobBuilder}
*/
public JobBuilder step(final Step step) {
jobElements.add(step);
return this;
}
/**
* Adds a {@linkplain Decision decision} to the job. The decision is typically built with {@link DecisionBuilder}.
*
* @param decision a pre-built decision
* @return this {@code JobBuilder}
*/
public JobBuilder decision(final Decision decision) {
jobElements.add(decision);
return this;
}
/**
* Adds a {@linkplain Flow flow} to the job. The flow is typically built with {@link FlowBuilder}.
*
* @param flow a pre-built flow
* @return this {@code JobBuilder}
*/
public JobBuilder flow(final Flow flow) {
jobElements.add(flow);
return this;
}
/**
* Adds a {@linkplain Split split} to the job. The split is typically built with {@link SplitBuilder}.
*
* @param split a pre-built split
* @return this {@code JobBuilder}
*/
public JobBuilder split(final Split split) {
jobElements.add(split);
return this;
}
/**
* Builds the job. This method also verifies the uniqueness of all id values within the job.
* After this method, this {@code JobBuilder} should not be used to build another job.
*
* @return a job built by this {@code JobBuilder}
*/
public Job build() {
final Job job = new Job(id);
if (restartable != null) {
job.setRestartable(restartable);
}
if (nameValues.size() > 0) {
job.setProperties(nameValuesToProperties());
}
job.setListeners(listeners);
ids.add(id);
for (final JobElement jobElement : jobElements) {
assertUniqueId(jobElement);
job.addJobElement(jobElement);
}
jobElements.clear();
ids.clear();
return job;
}
/**
* Creates {@link RefArtifact} with optional properties. If {@code props} is not null, it is taken as the artifact
* properties, and {@code propKeysValues} is ignored. If {@code props} is null, {@code propKeysValues} is taken as
* the artifact properties.
*
* @param ref batch artifact name
* @param props artifact properties, may be null
* @param propKeysValues optional artifact properties as a series of 2-element string arrays
*
* @return created {@code RefArtifact}
*/
static RefArtifact createRefArtifactWithProperties(final String ref,
final java.util.Properties props,
final String[]... propKeysValues) {
final RefArtifact refArtifact = new RefArtifact(ref);
final Properties properties = new Properties();
if (props != null) {
for (final String k : props.stringPropertyNames()) {
properties.add(k, props.getProperty(k));
}
} else if (propKeysValues.length > 0) {
for (final String[] pair : propKeysValues) {
properties.add(pair[0], pair.length > 1 ? pair[1] : null);
}
}
refArtifact.setProperties(properties);
return refArtifact;
}
/**
* Asserts all id values within a job are unique.
*
* @param jobElement a job element: step, decision, flow, or split
* @throws BatchRuntimeException in case of duplicate id values within a job
*/
private void assertUniqueId(final JobElement jobElement) throws BatchRuntimeException {
final String jobElementId = jobElement.getId();
if (!ids.add(jobElementId)) {
throw BatchMessages.MESSAGES.idAlreadyExists(jobElement.getClass().getSimpleName(), jobElementId);
}
if (jobElement instanceof Split) {
for (final Flow f : ((Split) jobElement).flows) {
assertUniqueId(f);
}
} else if (jobElement instanceof Flow) {
for (final JobElement e : ((Flow) jobElement).jobElements) {
assertUniqueId(e);
}
}
}
}