package org.openflamingo.mapreduce.etl.generate; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Counter; import org.apache.hadoop.mapreduce.CounterGroup; import org.apache.hadoop.mapreduce.Counters; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; import org.apache.hadoop.util.ToolRunner; import org.openflamingo.mapreduce.core.AbstractJob; import org.openflamingo.mapreduce.core.Delimiter; import org.openflamingo.mapreduce.etl.linecount.LineCountMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.TreeMap; import static org.openflamingo.mapreduce.core.Constants.JOB_FAIL; import static org.openflamingo.mapreduce.core.Constants.JOB_SUCCESS; /** * CSV 형식의 입력 파일에서 지정한 위치에 새로운 키값을 생성하는 Generate ETL Driver. * 이 MapReduce ETL은 다음의 파라미터를 가진다. * <ul> * <li><tt>inputDelimiter (id)</tt> - 입력 파일의 컬럼 구분자 (선택) (기본값 ,)</li> * <li><tt>outputDelimiter (od)</tt> - 출력 파일의 컬럼 구분자 (선택) (기본값 ,)</li> * <li><tt>sequenceIndex (si)</tt> - 시퀀스를 추가할 인덱스(0부터 시작) (선택) (기본값 0)</li> * <li><tt>startNumber (sn)</tt> - 시작할 시퀀스 번호(기본값 0) (선택) (기본값 0)</li> * <li><tt>columnSize (c)</tt> - 컬럼의 개수 (필수) (예; 4)</li> * <li><tt>sequenceType (st)</tt> - 시퀀스의 유형(SEQUENCE, TIMESTAMP) (필수)</li> * <li><tt>dateFormat (d)</tt> - 날짜 패턴(SimpleDateFormat) (선택) (기본값 yyyyMMdd-HHmmss-SSS)</li> * </ul> * * @author Edward KIM * @author Seo Ji Hye * @since 0.1 */ public class GenerateKeyDriver extends AbstractJob { /** * SLF4J API */ private static final Logger logger = LoggerFactory.getLogger(GenerateKeyDriver.class); public static void main(String[] args) throws Exception { int res = ToolRunner.run(new GenerateKeyDriver(), args); System.exit(res); } @Override public int run(String[] args) throws Exception { addInputOption(); addOutputOption(); addOption("inputDelimiter", "id", "입력 컬럼 구분자", Delimiter.COMMA.getDelimiter()); addOption("outputDelimiter", "od", "출력 컬럼 구분자", Delimiter.COMMA.getDelimiter()); addOption("sequenceIndex", "si", "시퀀스를 삽입할 컬럼의 인덱스(0부터 시작, 기본값 0)", "0"); addOption("startNumber", "sn", "시퀀스 번호의 시작값(기본값 0)", "0"); addOption("columnSize", "cs", "컬럼의 개수", true); addOption("generateType", "gt", "생성할 시퀀스의 유형(SEQUENCE, TIMESTAMP)", GenerateType.SEQUENCE.getType()); addOption("dateFormat", "df", "날짜 패턴(SimpleDateFormat)", /*FIXME*/"yyyyMMdd"); Map<String, String> parsedArgs = parseArguments(args); if (parsedArgs == null) { return JOB_FAIL; } //////////////////////////////////////// // Line Count Hadoop Job /////////////////////////////////////// // 임시 디렉토리를 가져온다. flamingo-mapreduce-site.xml 파일에 기본값이 정의되어 있다. Path temporaryPath = getTimestampTempPath(); logger.info("Temporary Path : {}", temporaryPath.toString()); Job lineCountJob = prepareJob( getInputPath(), temporaryPath, TextInputFormat.class, LineCountMapper.class, NullWritable.class, Text.class, TextOutputFormat.class); boolean step1 = lineCountJob.waitForCompletion(true); if (!step1) { return JOB_FAIL; } //////////////////////////////////////////////// // Calculating a start number per input split //////////////////////////////////////////////// // Counter에는 각 Mapper 별로 파일별 위치와 총 ROW의 개수를 포함하고 있다. Counters counters = lineCountJob.getCounters(); CounterGroup group = counters.getGroup(LineCountMapper.class.getName()); // 파일의 위치별로 정렬한다. TreeMap<Long, Long> counterMap = new TreeMap<Long, Long>(); for (Counter counter : group) { try { counterMap.put(Long.parseLong(counter.getName()), counter.getValue()); } catch (NumberFormatException ex) { } } //////////////////////////////////////// // Generate Sequence Hadoop Job /////////////////////////////////////// Job generateSequenceJob = prepareJob( getInputPath(), getOutputPath(), TextInputFormat.class, GenerateSequenceMapper.class, NullWritable.class, Text.class, TextOutputFormat.class); generateSequenceJob.getConfiguration().set("inputDelimiter", parsedArgs.get("--inputDelimiter")); generateSequenceJob.getConfiguration().set("outputDelimiter", parsedArgs.get("--outputDelimiter")); generateSequenceJob.getConfiguration().set("sequenceIndex", parsedArgs.get("--sequenceIndex")); generateSequenceJob.getConfiguration().set("generateType", parsedArgs.get("--generateType")); generateSequenceJob.getConfiguration().set("columnSize", parsedArgs.get("--columnSize")); if (GenerateType.valueOf(parsedArgs.get("--generateType")).equals(GenerateType.SEQUENCE)) { // SEQUENCE generateSequenceJob.getConfiguration().set("startNumber", parsedArgs.get("--startNumber")); int index = generateSequenceJob.getConfiguration().getInt("startNumber", 0); for (long position : counterMap.keySet()) { generateSequenceJob.getConfiguration().set(String.valueOf(position), String.valueOf(index)); index += counterMap.get(position); } } else { // TIMESTAMP generateSequenceJob.getConfiguration().set("dateFormat", parsedArgs.get("--dateFormat")); } boolean step2 = generateSequenceJob.waitForCompletion(true); if (!step2) { return JOB_FAIL; } try { // 임시 경로를 삭제한다. FileSystem.get(generateSequenceJob.getConfiguration()).delete(temporaryPath, true); logger.info("Now removed {}", temporaryPath.toString()); } catch (Exception ex) { // Exception handling is not need. } return JOB_SUCCESS; } }