package org.openflamingo.mapreduce.core; import com.google.common.base.Preconditions; import org.apache.commons.cli2.CommandLine; import org.apache.commons.cli2.Group; import org.apache.commons.cli2.Option; import org.apache.commons.cli2.OptionException; import org.apache.commons.cli2.builder.ArgumentBuilder; import org.apache.commons.cli2.builder.DefaultOptionBuilder; import org.apache.commons.cli2.builder.GroupBuilder; import org.apache.commons.cli2.commandline.Parser; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.Writable; import org.apache.hadoop.mapreduce.*; import org.apache.hadoop.util.ToolRunner; import org.apache.mahout.common.CommandLineUtil; import org.apache.mahout.common.commandline.DefaultOptionCreator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** * Flamingo MapReduce의 모든 Hadoop Job Driver의 최상위 클래스. * 기본적으로 MapReduce Driver는 사용자의 커맨드 라인에서 입력받은 파라미터를 Map과 Reduce Task에서 * 사용할 수 있도록 Hadoop Configuration에 설정해야 한다. 이 클래스는 * 모든 MapReduce Driver가 커맨드 라인을 파싱하고 사용하기 위한 표준 규격을 제공해준다. * <p/> * 커맨드 라인은 모든 자식 MapReduce Driver에서 다음과 같이 사용할 수 있다. * <ul> * <li>--tempDir (path): Job 동작시 임시로 사용하는 임시 디렉토리. (기본값 "<tt>/temp/${user.home}</tt>") * <li>--help: 도움말</li> * </ul> * <p/> * 추가적으로 MapReduce Job이 동작하기 위한 부가적인 파라미터는 다음과 같이 설정할 수 있다. * <p/> * <ul> * <li>-Dmapred.job.name=(name): Hadoop MapReduce Job의 이름. 기본으로 Driver 클래스명으로 설정.</li> * <li>-Dmapred.output.compress={true,false}: 출력 압축 여부 (기본값 true)</li> * <li>-Dmapred.input.dir=(path): 입력 파일 또는 입력 디렉토리 (필수)</li> * <li>-Dmapred.output.dir=(path): 출력 파일 (필수)</li> * </ul> * <tt>-D</tt>로 시작하는 모든 파라미터는 반드시 그렇지 않은 다른 파라미터 보다 앞서 사용해야 한다. */ public abstract class AbstractJob extends Configured implements WorkflowJob { /** * SLF4J API */ private static final Logger log = LoggerFactory.getLogger(AbstractJob.class); /** * MapReduce 입력 경로를 지정하기 위한 옵션. */ private Option inputOption; /** * MapReduce 출력 경로를 지정하기 위한 옵션. */ private Option outputOption; /** * {@link #parseArguments(String[])}을 통해 설정되는 MapReduce 입력 경로. */ private Path inputPath; /** * {@link #parseArguments(String[])}을 통해 설정되는 MapReduce 출력 경로. */ private Path outputPath; /** * {@link #parseArguments(String[])}을 통해 설정되는 MapReduce 임시 경로. * 이 경로는 CLASSPATH의 <tt>flamingo-mapreduce-site.xml</tt>에 <tt>tempDir</tt>로 지정되어 있는 값으로써 * <tt>/temp/${user.home}</tt>을 기본으로 한다. */ private Path tempPath; /** * MapReduce Job을 동작시키기 위한 파라미터의 Key Value Map. */ private Map<String, String> argMap; /** * 내부적으로 사용하기 위한 옵션 목록. */ private final List<Option> options; /** * 기본 생성자. */ protected AbstractJob() { options = new LinkedList<Option>(); if (getConf() == null) { setConf(new Configuration()); // Flamingo MapReduce의 기본 설정 파일을 로딩하여 Hadoop Configuration을 구성한다. getConf().addResource(getClass().getResource("/flamingo-mapreduce-site.xml")); } } /** * {@link #parseArguments(String[])} 호출을 통해서 생성된 입력 경로를 반환한다. * Hadoop MapReduce Driver에서는 {@link #addInputOption()} 메소드 호출을 통해서 * 입력 경로를 설정할 수 있으며 별도로 지정하지 않고 Hadoop Configuration에 * {@code mapred.input.dir} 설정값을 추가혀여 설정한다. * * @return 입력 디렉토리 */ protected Path getInputPath() { return inputPath; } /** * {@link #parseArguments(String[])} 호출을 통해서 생성된 출력 경로를 반환한다. * Hadoop MapReduce Driver에서는 {@link #addOutputOption()} 메소드 호출을 통해서 * 출력 경로를 설정할 수 있으며 별도로 지정하지 않고 Hadoop Configuration에 * {@code mapred.output.dir} 설정값을 추가혀여 설정한다. * * @return 출력 디렉토리 */ protected Path getOutputPath() { return outputPath; } /** * 지정한 출력 경로에 대해서 서브 디렉토리의 자식 경로를 반환한다. * * @param child 자식 경로 * @return Path */ protected Path getOutputPath(String child) { return new Path(outputPath, child); } /** * 자식 디렉토리를 갖는 임시 디렉토리를 반환한다. * * @param path 자식 디렉토리명 * @return 임시 경로 */ protected Path getTempPath(String path) { return new Path(getTempPath(), path); } /** * 임시 경로를 반환한다. * 임시 경로를 표현하는 설정값인 {@link Constants#TEMP_DIR}은 CLASSPATH의 <tt>flamingo-mapreudce-site.xml</tt> 파일에 정의되어 있으나 * 사용자가 직접 이 값을 수정하고자 하는 경우 커맨드 라인에서 <tt>--tempDir</tt>을 이용하여 지정할 수 있다. * 이 경우 이 메소드에서는 우선적으로 사용자가 설정한 것을 먼저 사용하게 된다. * 이 경우 Hadoop MapReduce Driver에서는 다음과 같이 코드를 작성하여 임시 디렉토리를 가져올 수 있다. * <p/> * <pre> * Path tempDir = getTempPath(); * </pre> * * @return 임시 경로 */ protected Path getTempPath() { String defaultTempDir = getConf().get("tempDir"); if (argMap.containsKey(keyFor(Constants.TEMP_DIR))) { defaultTempDir = argMap.get(keyFor(Constants.TEMP_DIR)); } return new Path(defaultTempDir); } /** * 현재 시간을 기준으로 한 임시 경로를 반환한다. 날짜 패턴은 * <tt>flamingo-mapreduce-site.xml</tt> 파일에 <tt>tempDir.date.pattern</tt>으로 * 설정할 수 있으며 기본으로 <tt>yyyyMMdd-HHmmss-SSS</tt>을 사용한다. * * @return 임시 경로 */ protected Path getTimestampTempPath() { SimpleDateFormat formatter = new SimpleDateFormat(getConf().get("tempDir.date.pattern")); return getTempPath(formatter.format(new Date())); } /** * Prefix를 가진 임시 경로를 반환한다. * * @param prefix Prefix * @param path 자식 디렉토리명 * @return 임시 경로 */ protected Path getTempPath(String prefix, String path) { Path tempPath = getTempPath(); Path prefixPath = new Path(tempPath, prefix); return new Path(prefixPath, path); } /** * 인자가 없는 옵션을 추가한다. 인자가 없으므로 키가 포함되어 있는지 여부로 존재 여부를 판단한다. * * @param name 파라미터명(예; <tt>inputPath</tt>) * @param shortName 출약 파라미터명(예; <tt>i</tt>) * @param description 파라미터에 대한 설명문 */ protected void addFlag(String name, String shortName, String description) { options.add(buildOption(name, shortName, description, false, false, null)); } /** * 기본값이 없는 옵션을 추가한다. 이 옵션은 필수 옵션이 아니다. * * @param name 파라미터명(예; <tt>inputPath</tt>) * @param shortName 출약 파라미터명(예; <tt>i</tt>) * @param description 파라미터에 대한 설명문 */ protected void addOption(String name, String shortName, String description) { options.add(buildOption(name, shortName, description, true, false, null)); } /** * Hadoop MapReduce에 옵션을 추가한다. 옵션 추가는 커맨드 라인을 통해서 가능하며 * 커맨드 라인은 {@link #parseArguments(String[])} 메소드를 호출하여 파싱하게 된다. * * @param name 파라미터명(예; <tt>inputPath</tt>) * @param shortName 출약 파라미터명(예; <tt>i</tt>) * @param description 파라미터에 대한 설명문 * @param required 이 값이 <tt>true</tt>인 경우 커맨드 라인에서 입력 파라미터를 지정하지 않는 경우 * 예외를 발생시킨다. 사용법과 에러 메시지를 포함한 예외를 던진다. */ protected void addOption(String name, String shortName, String description, boolean required) { options.add(buildOption(name, shortName, description, true, required, null)); } /** * Hadoop MapReduce에 옵션을 추가한다. 옵션 추가는 커맨드 라인을 통해서 가능하며 * 커맨드 라인은 {@link #parseArguments(String[])} 메소드를 호출하여 파싱하게 된다. * * @param name 파라미터명(예; <tt>inputPath</tt>) * @param shortName 출약 파라미터명(예; <tt>i</tt>) * @param description 파라미터에 대한 설명문 * @param defaultValue 커맨드 라인에서 입력 인자를 지정하지 않는 경우 설정할 기본값으로써 null을 허용한다. */ protected void addOption(String name, String shortName, String description, String defaultValue) { options.add(buildOption(name, shortName, description, true, false, defaultValue)); } /** * Hadoop MapReduce에 옵션을 추가한다. 옵션 추가는 커맨드 라인을 통해서 가능하며 * 커맨드 라인은 {@link #parseArguments(String[])} 메소드를 호출하여 파싱하게 된다. * 만약에 옵션이 인자를 가지고 있지 않다면 {@code parseArguments} 메소드 호출을 통해 리턴한 * map의 {@code containsKey} 메소드를 통해서 옵션의 존재 여부를 확인하게 된다. * 그렇지 않은 경우 옵션의 영문 옵션명 앞에 '--'을 붙여서 map에서 해당 키가 존재하는지 확인한 후 * 존재하는 경우 해당 옵션의 문자열값을 사용한다. * * @param option 추가할 옵션 * @return 추가한 옵션 */ protected Option addOption(Option option) { options.add(option); return option; } /** * 기본 입력 디렉토리 옵션을 추가한다. 기본 입력 디렉토리는 커맨드 라인에서 <tt>'-i'</tt>를 지정함으로써 * 가능하며 {@link #parseArguments(String[])} 메소드를 호출했을 때 이 옵션을 기준으로 * 입력 경로가 설정된다. 기본적으로 입력 경로는 모든 Hadoop Job이 시작하기 위해서 반드시 필요하므로 * 이 옵션은 기본으로 <tt>required</tt> 속서을 갖는다. */ protected void addInputOption() { this.inputOption = addOption(DefaultOptionCreator.inputOption().create()); } /** * 기본 출력 디렉토리 옵션을 추가한다. 기본 출력 디렉토리는 커맨드 라인에서 <tt>'-o'</tt>를 지정함으로써 * 가능하며 {@link #parseArguments(String[])} 메소드를 호출했을 때 이 옵션을 기준으로 * 입력 경로가 설정된다. 기본적으로 입력 경로는 모든 Hadoop Job이 시작하기 위해서 반드시 필요하므로 * 이 옵션은 기본으로 <tt>required</tt> 속서을 갖는다. */ protected void addOutputOption() { this.outputOption = addOption(DefaultOptionCreator.outputOption().create()); } /** * 지정한 파라미터를 가진 옵션을 구성한다. 이름과 설명은 필수로 입력해야 한다. * required. * * @param name 커맨드 라인에서 '--'을 prefix로 가진 옵션의 이름 * @param shortName 커맨드 라인에서 '--'을 prefix로 가진 옵션의 짧은 이름 * @param description 도움말에 출력할 옵션에 대한 상세 설명 * @param hasArg 인자를 가진다면 <tt>true</tt> * @param required 필수 옵션이라면 <tt>true</tt> * @param defaultValue 인자의 기본값. <tt>null</tt>을 허용한다. * @return 옵션 */ protected static Option buildOption(String name, String shortName, String description, boolean hasArg, boolean required, String defaultValue) { DefaultOptionBuilder optBuilder = new DefaultOptionBuilder().withLongName(name).withDescription(description) .withRequired(required); if (shortName != null) { optBuilder.withShortName(shortName); } if (hasArg) { ArgumentBuilder argBuilder = new ArgumentBuilder().withName(name).withMinimum(1).withMaximum(1); if (defaultValue != null) { argBuilder = argBuilder.withDefault(defaultValue); } optBuilder.withArgument(argBuilder.create()); } return optBuilder.create(); } /** * 사용자가 입력한 커맨드 라인을 파싱한다. * 만약에 <tt>-h</tt>를 지정하거나 예외가 발생하는 경우 도움말을 출력하고 <tt>null</tt>을 반환한다. * * @param args 커맨드 라인 옵션 * @return 인자와 인자에 대한 값을 포함하는 {@code Map<String,String>}. * 인자의 key는 옵션명에 되며 옵션명은 '--'을 prefix로 갖는다. * 따라서 옵션을 기준으로 {@code Map<String,String>} 에서 찾고자 하는 경우 반드시 옵션명에 '--'을 붙이도록 한다. */ public Map<String, String> parseArguments(String[] args) { Option helpOpt = addOption(DefaultOptionCreator.helpOption()); addOption("tempDir", null, "임시 디렉토리", false); addOption("startPhase", null, "실행할 첫번쨰 단계", "0"); addOption("endPhase", null, "실행할 마지막 단계", String.valueOf(Integer.MAX_VALUE)); GroupBuilder groupBuilder = new GroupBuilder().withName("Hadoop MapReduce Job 옵션:"); for (Option opt : options) { groupBuilder = groupBuilder.withOption(opt); } Group group = groupBuilder.create(); CommandLine cmdLine; try { Parser parser = new Parser(); parser.setGroup(group); parser.setHelpOption(helpOpt); cmdLine = parser.parse(args); } catch (OptionException e) { log.error(e.getMessage()); CommandLineUtil.printHelpWithGenericOptions(group, e); return null; } if (cmdLine.hasOption(helpOpt)) { CommandLineUtil.printHelpWithGenericOptions(group); return null; } try { parseDirectories(cmdLine); } catch (IllegalArgumentException e) { log.error(e.getMessage()); CommandLineUtil.printHelpWithGenericOptions(group); return null; } argMap = new TreeMap<String, String>(); maybePut(argMap, cmdLine, this.options.toArray(new Option[this.options.size()])); log.info("Command line arguments: ", argMap); Set<String> keySet = argMap.keySet(); for (Iterator<String> iterator = keySet.iterator(); iterator.hasNext(); ) { String key = iterator.next(); log.info(" {} = {}", key, argMap.get(key)); } return argMap; } /** * 지정한 옵션명에 대해서 옵션 키를 구성한다. 예를 들여 옵션명이 <tt>name</tt> 이라면 실제 옵션 키는 <tt>--name</tt>이 된다. * * @param optionName 옵션명 */ public static String keyFor(String optionName) { return "--" + optionName; } /** * 지정한 옵션에 대해서 Option 객체를 반환한다. * * @return 요청한 옵션이 존재하는 경우 <tt>Option</tt>, 그렇지 않다면 <tt>null</tt>을 반환한다. */ public String getOption(String optionName) { return argMap.get(keyFor(optionName)); } /** * 지정한 옵션이 존재하는지 확인한다. * * @return 요청한 옵션이 존재하는 경우 <tt>true</tt> */ public boolean hasOption(String optionName) { return argMap.containsKey(keyFor(optionName)); } /** * 커맨드 라인 옵션 또는 Hadoop Configuration 파라미터에서 입출력 파일 및 디렉토리를 획득한다. * {@code addInputOption} 또는 {@code addOutputOption} 메소드를 호출할 때 * 커맨드 라인 또는 Hadoop Configuration에 입출력 경로 및 파일값이 존재하지 않는 경우 * {@code OptionException}을 던진다. 만약에 Hadoop Configuration 속성으로 * {@code inputPath} 또는 {@code outputPath}을 지정하는 경우 * <tt>non-null</tt>만 사용할 수 있다. 커맨드 라인 옵션은 Hadoop Configuration 속성보다 * 앞서서 사용할 수 있다. * * @param cmdLine 커맨드 라인 * @throws IllegalArgumentException inputOption 또는 {@code --input}와 {@code -Dmapred.input dir} 둘 줄 하나라도 존재하지 않는 경우, outputOption 또는 {@code --output}와 {@code -Dmapred.output dir} 둘 줄 하나라도 존재하지 않는 경우, */ protected void parseDirectories(CommandLine cmdLine) { Configuration conf = getConf(); if (inputOption != null && cmdLine.hasOption(inputOption)) { this.inputPath = new Path(cmdLine.getValue(inputOption).toString()); } if (inputPath == null && conf.get("mapred.input.dir") != null) { this.inputPath = new Path(conf.get("mapred.input.dir")); } if (outputOption != null && cmdLine.hasOption(outputOption)) { this.outputPath = new Path(cmdLine.getValue(outputOption).toString()); } if (outputPath == null && conf.get("mapred.output.dir") != null) { this.outputPath = new Path(conf.get("mapred.output.dir")); } // Temporary Path를 설정한다. 기본값은 CLASSPATH의 <tt>flamingo-mapreduce-site.xml</tt> 파일에 있는 값을 사용한다. if (tempPath == null && conf.get("tempDir") != null) { this.tempPath = new Path(conf.get("tempDir")); } Preconditions.checkArgument(inputOption == null || inputPath != null, "입력 디렉토리가 지정되어 있지 않거나 -Dmapred.input.dir 파라미터가 잘못 지정되어 있습니다. -Dmapred.input.dir 옵션은 다른 옵션을 사용하기 전에 제일 앞에 사용해야 합니다."); Preconditions.checkArgument(outputOption == null || outputPath != null, "출력 디렉토리가 지정되어 있지 않거나 -Dmapred.output.dir 파라미터가 잘못 지정되어 있습니다. -Dmapred.input.dir 옵션은 다른 옵션을 사용하기 전에 제일 앞에 사용해야 합니다."); } protected static void maybePut(Map<String, String> args, CommandLine cmdLine, Option... opt) { for (Option o : opt) { // 커맨드 라인에 옵션이 있거나 기본값과 같은 값이 있는 경우 if (cmdLine.hasOption(o) || cmdLine.getValue(o) != null) { // 커맨드 라인의 옵션에 값이 OK // nulls are ok, for cases where options are simple flags. Object vo = cmdLine.getValue(o); String value = vo == null ? null : vo.toString(); args.put(o.getPreferredName(), value); } } } /** * 여려 단계의 Job을 실행하는 경우 다음 Phase을 실행할지 여부를 결정한다. * * @param args Key Value 파라미터 맵 * @param currentPhase Phase * @return 더 실행해야 하는지 여부. 더 실행해야 하는 경우 <tt>true</tt>를 반환. */ protected static boolean shouldRunNextPhase(Map<String, String> args, AtomicInteger currentPhase) { int phase = currentPhase.getAndIncrement(); String startPhase = args.get("--startPhase"); String endPhase = args.get("--endPhase"); boolean phaseSkipped = (startPhase != null && phase < Integer.parseInt(startPhase)) || (endPhase != null && phase > Integer.parseInt(endPhase)); if (phaseSkipped) { log.info("Skipping phase {}", phase); } return !phaseSkipped; } /** * Mappre, Reducer 기반 Hadoop Job을 생성한다. * * @param inputPath 입력 경로 * @param outputPath 출력 경로 * @param inputFormat 입력 포맷 * @param mapper Mapper * @param mapperKey Mapper의 Output Key Class * @param mapperValue Mapper의 Output Value Class * @param reducer Reducer * @param reducerKey Reducer의 Output Key Class * @param reducerValue Reducer의 Output Value Class * @param outputFormat 출력 포맷 * @return Hadoop Job * @throws IOException Hadoop Job을 생성할 수 없는 경우 */ protected Job prepareJob(Path inputPath, Path outputPath, Class<? extends InputFormat> inputFormat, Class<? extends Mapper> mapper, Class<? extends Writable> mapperKey, Class<? extends Writable> mapperValue, Class<? extends Reducer> reducer, Class<? extends Writable> reducerKey, Class<? extends Writable> reducerValue, Class<? extends OutputFormat> outputFormat) throws IOException { Job job = new Job(new Configuration(getConf())); Configuration jobConf = job.getConfiguration(); if (reducer.equals(Reducer.class)) { if (mapper.equals(Mapper.class)) { throw new IllegalStateException("Can't figure out the user class jar file from mapper/reducer"); } job.setJarByClass(mapper); } else { job.setJarByClass(reducer); } job.setInputFormatClass(inputFormat); jobConf.set("mapred.input.dir", inputPath.toString()); job.setMapperClass(mapper); job.setMapOutputKeyClass(mapperKey); job.setMapOutputValueClass(mapperValue); jobConf.setBoolean("mapred.compress.map.output", true); job.setReducerClass(reducer); job.setOutputKeyClass(reducerKey); job.setOutputValueClass(reducerValue); job.setJobName(getCustomJobName(job, mapper, reducer)); job.setOutputFormatClass(outputFormat); jobConf.set("mapred.output.dir", outputPath.toString()); return job; } /** * Mapper, Reducer 기반 Hadoop Job을 생성한다. * * @param inputPath 입력 경로 * @param outputPath 출력 경로 * @param inputFormat 입력 포맷 * @param mapper Mapper * @param mapperKey Mapper의 Output Key Class * @param mapperValue Mapper의 Output Value Class * @param reducer Reducer * @param reducerKey Reducer의 Output Key Class * @param reducerValue Reducer의 Output Value Class * @param outputFormat 출력 포맷 * @param reduceTask Reducer의 개수 * @return Hadoop Job * @throws IOException Hadoop Job을 생성할 수 없는 경우 */ protected Job prepareJob(Path inputPath, Path outputPath, Class<? extends InputFormat> inputFormat, Class<? extends Mapper> mapper, Class<? extends Writable> mapperKey, Class<? extends Writable> mapperValue, Class<? extends Reducer> reducer, Class<? extends Writable> reducerKey, Class<? extends Writable> reducerValue, Class<? extends OutputFormat> outputFormat, int reduceTask) throws IOException { Job job = new Job(new Configuration(getConf())); Configuration jobConf = job.getConfiguration(); if (reducer.equals(Reducer.class)) { if (mapper.equals(Mapper.class)) { throw new IllegalStateException("Can't figure out the user class jar file from mapper/reducer"); } job.setJarByClass(mapper); } else { job.setJarByClass(reducer); } job.setInputFormatClass(inputFormat); jobConf.set("mapred.input.dir", inputPath.toString()); job.setMapperClass(mapper); job.setMapOutputKeyClass(mapperKey); job.setMapOutputValueClass(mapperValue); jobConf.setBoolean("mapred.compress.map.output", true); job.setReducerClass(reducer); job.setOutputKeyClass(reducerKey); job.setOutputValueClass(reducerValue); job.setJobName(getCustomJobName(job, mapper, reducer)); job.setNumReduceTasks(reduceTask); job.setOutputFormatClass(outputFormat); jobConf.set("mapred.output.dir", outputPath.toString()); return job; } /** * Mapper 기반 Hadoop Job을 생성한다. * 이 메소드는 Mapper로만 구성된 MapReduce Job을 생서할 때 사용한다. * * @param inputPath 입력 경로 * @param outputPath 출력 경로 * @param inputFormat 입력 포맷 * @param mapper Mapper * @param mapperKey Mapper의 Output Key Class * @param mapperValue Mapper의 Output Value Class * @param outputFormat 출력 포맷 * @return Hadoop Job * @throws IOException Hadoop Job을 생성할 수 없는 경우 */ protected Job prepareJob(Path inputPath, Path outputPath, Class<? extends InputFormat> inputFormat, Class<? extends Mapper> mapper, Class<? extends Writable> mapperKey, Class<? extends Writable> mapperValue, Class<? extends OutputFormat> outputFormat) throws IOException { Job job = new Job(new Configuration(getConf())); Configuration jobConf = job.getConfiguration(); if (mapper.equals(Mapper.class)) { throw new IllegalStateException("Can't figure out the user class jar file from mapper"); } job.setJarByClass(mapper); job.setInputFormatClass(inputFormat); jobConf.set("mapred.input.dir", inputPath.toString()); job.setMapperClass(mapper); job.setMapOutputKeyClass(mapperKey); job.setMapOutputValueClass(mapperValue); jobConf.setBoolean("mapred.compress.map.output", true); job.setNumReduceTasks(0); job.setJobName(getCustomJobName(job, mapper)); job.setOutputFormatClass(outputFormat); jobConf.set("mapred.output.dir", outputPath.toString()); return job; } /** * Mapper와 Reducer를 기반으로 Hadoop Job 이름을 반환한다. * * @param job Hadoop Job * @param mapper {@link org.apache.hadoop.mapreduce.Mapper} * @param reducer Reducer * @return Job Name */ private String getCustomJobName(JobContext job, Class<? extends Mapper> mapper, Class<? extends Reducer> reducer) { StringBuilder name = new StringBuilder(100); String customJobName = job.getJobName(); if (customJobName == null || customJobName.trim().length() == 0) { name.append(getClass().getSimpleName()); } else { name.append(customJobName); } return name.toString(); } /** * Mapper를 기반으로 Hadoop Job 이름을 반환한다. * * @param job Hadoop Job * @param mapper {@link org.apache.hadoop.mapreduce.Mapper} * @return Job Name */ private String getCustomJobName(JobContext job, Class<? extends Mapper> mapper) { StringBuilder name = new StringBuilder(100); String customJobName = job.getJobName(); if (customJobName == null || customJobName.trim().length() == 0) { name.append(getClass().getSimpleName()); } else { name.append(customJobName); } return name.toString(); } /** * 외부 엔진이 ETL Driver를 실행하기 위해 호출하게 되는 메서드. * * @param params Key Value 형식의 MapReduce Job 파라미터 * @return * @throws Exception */ @Override public int execute(Map<String, String> params) throws Exception { String[] args = new String[params.size() * 2]; Set<String> keySet = params.keySet(); int i = 0; for (Iterator<String> iterator = keySet.iterator(); iterator.hasNext(); ) { String key = iterator.next(); String value = params.get(key); args[i] = key; args[++i] = value; i++; } return run(args); } /** * 외부 엔진이 ETL Driver를 실행하기 위해 호출하게 되는 메서드. * * @param mapreduceParams Key Value 형식의 MapReduce Job 파라미터 * @return * @throws Exception */ @Override public int execute(Map<String, String> mapreduceParams, Map<String, String> globalParams) throws Exception { String[] args = new String[globalParams.size() + mapreduceParams.size() * 2]; // parse global parameters Set<String> globalKeySet = globalParams.keySet(); int i = 0; for (Iterator<String> iterator = globalKeySet.iterator(); iterator.hasNext(); ) { String key = iterator.next(); String value = globalParams.get(key); args[i] = "-D" + key + "=" + value; i++; } // parse map reduce parameters Set<String> mapreduceKeySet = mapreduceParams.keySet(); for (Iterator<String> iterator = mapreduceKeySet.iterator(); iterator.hasNext(); ) { String key = iterator.next(); String value = mapreduceParams.get(key); args[i] = key; args[++i] = value; i++; } // Let ToolRunner handle generic command-line options return ToolRunner.run(this, args); } /** * 입력한 Key Value로 커맨드 라인 입력 옵션을 구성한다. 이 메소드는 * Workflow Engine에서 넘어온 Key Value 형태의 Map을 커맨드 라인으로 구성하기 위해서 사용하며 그 이외의 경우 * 사용할 필요가 없다. * * @param key MapReduce Job의 파라미터 Key * @param value MapReduce Job의 파라미터 Key의 Value * @return 커맨드 라인 입력 옵션 */ public static String getKeyValuePair(String key, String value) { return keyFor(key) + " " + value; } }