/**
* (c) Copyright 2012 WibiData, Inc.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.kiji.mapreduce.tools.framework;
import java.io.IOException;
import java.util.List;
import com.google.common.base.Preconditions;
import org.kiji.annotations.ApiAudience;
import org.kiji.annotations.ApiStability;
import org.kiji.annotations.Inheritance;
import org.kiji.common.flags.Flag;
import org.kiji.mapreduce.MapReduceJobInput;
import org.kiji.mapreduce.MapReduceJobOutput;
import org.kiji.mapreduce.framework.MapReduceJobBuilder;
import org.kiji.schema.platform.SchemaPlatformBridge;
import org.kiji.schema.tools.BaseTool;
/**
* Base class for tools that run MapReduce jobs.
*
* @param <B> The type of job builder to use.
*/
@ApiAudience.Framework
@ApiStability.Evolving
@Inheritance.Extensible
public abstract class JobTool<B extends MapReduceJobBuilder> extends BaseTool {
// TODO(KIJIMR-62): Better usage doc:
@Flag(name="input",
usage="Job input specification: --input=\"format=<input-format> ...\".")
protected String mInputFlag = "";
@Flag(name="output",
usage="Job output specification: --output=\"format=<output-format> nsplits=N ...\"")
protected String mOutputFlag = "";
@Flag(name="lib",
usage="A directory of jars for including user code.\n"
+ "\tUnqualified paths are resolved in the local filesystem.")
protected String mLibDir = "";
@Flag(name="kvstores", usage="KeyValueStore specification XML file to attach to the job")
protected String mKvStoreFile = "";
private MapReduceJobInput mJobInput;
private MapReduceJobOutput mJobOutput;
/** {@inheritDoc} */
@Override
protected void validateFlags() throws Exception {
Preconditions.checkArgument(!mInputFlag.isEmpty(),
"Specify an input to the job with --input=...");
Preconditions.checkArgument(!mOutputFlag.isEmpty(),
"Specify an output to the job with --output=...");
mJobInput = MapReduceJobInputFactory.create().fromSpaceSeparatedMap(mInputFlag);
mJobOutput = MapReduceJobOutputFactory.create().fromSpaceSeparatedMap(mOutputFlag);
}
/**
* Creates a new job builder.
*
* @return A new job builder.
*/
protected abstract B createJobBuilder();
/**
* Configures the job builder by applying the flags from the user.
*
* @param jobBuilder The job builder to configure.
* @throws ClassNotFoundException If a mapper, reducer, or other user class can not be loaded.
* @throws IOException if there is an error reading configuration input data.
* @throws JobIOSpecParseException If the input or output for the job can not configured.
*/
protected void configure(B jobBuilder)
throws ClassNotFoundException, IOException {
// Get Hadoop config files (hdfs-site.xml, etc) into Configuration objects.
// The Configuration for this instance will also get the new default values as
// an underlay.
SchemaPlatformBridge.get().initializeHadoopResources();
// Use default environment configuration:
jobBuilder.withConf(getConf());
// Add user dependency jars if specified.
if (!mLibDir.isEmpty()) {
jobBuilder.addJarDirectory(mLibDir);
}
// Add user-specified KVStore definitions, if specified.
if (!mKvStoreFile.isEmpty()) {
jobBuilder.withStoreBindingsFile(mKvStoreFile);
}
}
/** {@inheritDoc} */
@Override
protected int run(List<String> nonFlagArgs) throws Exception {
final B jobBuilder = createJobBuilder();
Preconditions.checkNotNull(jobBuilder, "Internal error: unable to create job builder?");
configure(jobBuilder);
try {
final boolean success = jobBuilder.build().run();
if (success) {
getPrintStream().println("Job successful");
} else {
getPrintStream().println("Job failed!");
}
return success ? 0 : 1;
} catch (RuntimeException exception) {
getPrintStream().println("\nJob failed with exception: " + exception.getMessage());
throw exception;
}
}
/** @return the job input. */
protected final MapReduceJobInput getJobInput() {
return mJobInput;
}
/** @return the job output. */
protected final MapReduceJobOutput getJobOutput() {
return mJobOutput;
}
}