/**
* 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.compiler.testing;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.compiler.batch.BatchCompiler;
import com.asakusafw.compiler.batch.BatchCompilerConfiguration;
import com.asakusafw.compiler.batch.BatchDriver;
import com.asakusafw.compiler.batch.Workflow;
import com.asakusafw.compiler.batch.processor.JobFlowWorkDescriptionProcessor;
import com.asakusafw.compiler.common.Precondition;
import com.asakusafw.compiler.flow.FlowCompilerOptions;
import com.asakusafw.compiler.flow.Location;
import com.asakusafw.compiler.flow.jobflow.CompiledStage;
import com.asakusafw.compiler.flow.jobflow.JobflowModel;
import com.asakusafw.compiler.repository.SpiDataClassRepository;
import com.asakusafw.compiler.repository.SpiExternalIoDescriptionProcessorRepository;
import com.asakusafw.compiler.repository.SpiFlowElementProcessorRepository;
import com.asakusafw.compiler.repository.SpiFlowGraphRewriterRepository;
import com.asakusafw.compiler.repository.SpiWorkflowProcessorRepository;
import com.asakusafw.utils.graph.Graph;
import com.asakusafw.utils.graph.Graphs;
import com.asakusafw.utils.java.model.util.Models;
import com.asakusafw.vocabulary.batch.BatchDescription;
import com.asakusafw.vocabulary.batch.JobFlowWorkDescription;
/**
* Compiles batch classes and generates batch applications.
*/
public final class DirectBatchCompiler {
static final Logger LOG = LoggerFactory.getLogger(DirectBatchCompiler.class);
/**
* Compiles the target batch class and returns its structural information.
* @param batchClass the target batch class
* @param basePackageName the base package name of generated Java source files
* @param clusterWorkingDirectory the runtime working directory
* @param outputDirectory the output directory
* @param localWorkingDirectory the working directory for compiler
* @param extraResources the extra resources for embedding contents into each jobflow package file
* @param serviceClassLoader the class loader for loading compiler services
* @param flowCompilerOptions the compiler options for flow DSL
* @return the compile results
* @throws IOException if failed to compile
* @throws IllegalArgumentException if the parameters are {@code null}
*/
public static BatchInfo compile(
Class<? extends BatchDescription> batchClass,
String basePackageName,
Location clusterWorkingDirectory,
File outputDirectory,
File localWorkingDirectory,
List<File> extraResources,
ClassLoader serviceClassLoader,
FlowCompilerOptions flowCompilerOptions) throws IOException {
Precondition.checkMustNotBeNull(batchClass, "batchClass"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(clusterWorkingDirectory, "clusterWorkingDirectory"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(outputDirectory, "outputDirectory"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(localWorkingDirectory, "localWorkingDirectory"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(extraResources, "extraResources"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(serviceClassLoader, "serviceClassLoader"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(flowCompilerOptions, "flowCompilerOptions"); //$NON-NLS-1$
if (localWorkingDirectory.exists()) {
delete(localWorkingDirectory);
}
if (outputDirectory.exists()) {
delete(outputDirectory);
}
BatchDriver driver = BatchDriver.analyze(batchClass);
if (driver.hasError()) {
throw new IOException(driver.getDiagnostics().toString());
}
String batchId = driver.getBatchClass().getConfig().name();
BatchCompilerConfiguration config = createConfig(
batchId,
basePackageName,
clusterWorkingDirectory,
outputDirectory,
localWorkingDirectory,
extraResources,
serviceClassLoader,
flowCompilerOptions);
BatchCompiler compiler = new BatchCompiler(config);
Workflow workflow = compiler.compile(driver.getBatchClass().getDescription());
return toInfo(workflow, outputDirectory);
}
/**
* Returns the structural information of the batch application from a compiled workflow.
* @param workflow the target workflow
* @param outputDirectory the output directory
* @return the structural information
* @throws IllegalArgumentException if the parameters are {@code null}
*/
public static BatchInfo toInfo(Workflow workflow, File outputDirectory) {
Precondition.checkMustNotBeNull(workflow, "workflow"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(outputDirectory, "outputDirectory"); //$NON-NLS-1$
List<JobflowInfo> jobflows = new ArrayList<>();
for (Workflow.Unit unit : Graphs.sortPostOrder(workflow.getGraph())) {
JobflowInfo jobflow = toJobflow(unit, outputDirectory);
if (jobflow != null) {
jobflows.add(jobflow);
}
}
return new BatchInfo(workflow, outputDirectory, jobflows);
}
private static boolean delete(File target) {
assert target != null;
boolean success = true;
if (target.isDirectory()) {
for (File child : list(target)) {
success &= delete(child);
}
}
success &= target.delete();
return success;
}
private static List<File> list(File file) {
return Optional.ofNullable(file.listFiles())
.map(Arrays::asList)
.orElse(Collections.emptyList());
}
/**
* Creates a compiler configuration object.
* @param batchId the target batch ID
* @param basePackageName the base package name of generated Java source files
* @param clusterWorkingLocation the runtime working directory
* @param outputDirectory the output directory
* @param localWorkingDirectory the working directory for compiler
* @param extraResources the extra resources for embedding contents into each jobflow package file
* @param serviceClassLoader the class loader for loading compiler services
* @param flowCompilerOptions the compiler options for flow DSL
* @return the created object
* @throws IOException if failed to extract the configuration
* @throws IllegalArgumentException if the parameters are {@code null}
*/
public static BatchCompilerConfiguration createConfig(
String batchId,
String basePackageName,
Location clusterWorkingLocation,
File outputDirectory,
File localWorkingDirectory,
List<File> extraResources,
ClassLoader serviceClassLoader,
FlowCompilerOptions flowCompilerOptions) throws IOException {
assert batchId != null;
assert basePackageName != null;
assert clusterWorkingLocation != null;
assert outputDirectory != null;
assert localWorkingDirectory != null;
assert extraResources != null;
assert serviceClassLoader != null;
assert flowCompilerOptions != null;
BatchCompilerConfiguration config = new BatchCompilerConfiguration();
config.setBatchId(batchId);
config.setDataClasses(new SpiDataClassRepository());
config.setExternals(new SpiExternalIoDescriptionProcessorRepository());
config.setGraphRewriters(new SpiFlowGraphRewriterRepository());
config.setFactory(Models.getModelFactory());
config.setFlowElements(new SpiFlowElementProcessorRepository());
config.setLinkingResources(DirectFlowCompiler.createRepositories(serviceClassLoader, extraResources));
config.setOutputDirectory(outputDirectory);
config.setRootLocation(clusterWorkingLocation);
config.setRootPackageName(basePackageName);
config.setWorkflows(new SpiWorkflowProcessorRepository());
config.setServiceClassLoader(serviceClassLoader);
config.setWorkingDirectory(localWorkingDirectory);
config.setFlowCompilerOptions(flowCompilerOptions);
return config;
}
private static JobflowInfo toJobflow(
Workflow.Unit unit,
File outputDirectory) {
assert unit != null;
assert outputDirectory != null;
if ((unit.getDescription() instanceof JobFlowWorkDescription) == false) {
return null;
}
JobflowModel model = (JobflowModel) unit.getProcessed();
String flowId = model.getFlowId();
return new JobflowInfo(
model,
JobFlowWorkDescriptionProcessor.getPackageLocation(outputDirectory, flowId),
JobFlowWorkDescriptionProcessor.getSourceLocation(outputDirectory, flowId),
toStagePlan(model));
}
private static List<StageInfo> toStagePlan(JobflowModel jobflow) {
assert jobflow != null;
List<StageInfo> results = new ArrayList<>();
for (CompiledStage compiled : jobflow.getCompiled().getPrologueStages()) {
results.add(toInfo(compiled));
}
Graph<JobflowModel.Stage> depenedencies = jobflow.getDependencyGraph();
for (JobflowModel.Stage stage : Graphs.sortPostOrder(depenedencies)) {
results.add(toInfo(stage.getCompiled()));
}
for (CompiledStage compiled : jobflow.getCompiled().getEpilogueStages()) {
results.add(toInfo(compiled));
}
return results;
}
private static StageInfo toInfo(CompiledStage stage) {
assert stage != null;
String className = stage.getQualifiedName().toNameString();
return new StageInfo(className);
}
private DirectBatchCompiler() {
return;
}
}