/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.accumulo.tserver.log; import java.io.EOFException; import java.io.IOException; import java.util.Objects; import org.apache.accumulo.server.fs.VolumeManager; import org.apache.accumulo.server.log.SortedLogState; import org.apache.commons.collections.buffer.PriorityBuffer; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.DataInputBuffer; import org.apache.hadoop.io.DataOutputBuffer; import org.apache.hadoop.io.MapFile.Reader; import org.apache.hadoop.io.Writable; import org.apache.hadoop.io.WritableComparable; /** * Provide simple Map.Reader methods over multiple Maps. * * Presently only supports next() and seek() and works on all the Map directories within a directory. The primary purpose of this class is to merge the results * of multiple Reduce jobs that result in Map output files. */ @SuppressWarnings({"rawtypes", "unchecked"}) public class MultiReader { /** * Group together the next key/value from a Reader with the Reader * */ private static class Index implements Comparable<Index> { Reader reader; WritableComparable key; Writable value; boolean cached = false; private static Object create(java.lang.Class<?> klass) { try { return klass.getConstructor().newInstance(); } catch (Throwable t) { throw new RuntimeException("Unable to construct objects to use for comparison"); } } public Index(Reader reader) { this.reader = reader; key = (WritableComparable) create(reader.getKeyClass()); value = (Writable) create(reader.getValueClass()); } private void cache() throws IOException { if (!cached && reader.next(key, value)) { cached = true; } } @Override public int hashCode() { return Objects.hashCode(key); } @Override public boolean equals(Object obj) { return this == obj || (obj != null && obj instanceof Index && 0 == compareTo((Index) obj)); } @Override public int compareTo(Index o) { try { cache(); o.cache(); // no more data: always goes to the end if (!cached) return 1; if (!o.cached) return -1; return key.compareTo(o.key); } catch (IOException ex) { throw new RuntimeException(ex); } } } private PriorityBuffer heap = new PriorityBuffer(); public MultiReader(VolumeManager fs, Path directory) throws IOException { boolean foundFinish = false; for (FileStatus child : fs.listStatus(directory)) { if (child.getPath().getName().startsWith("_")) continue; if (SortedLogState.isFinished(child.getPath().getName())) { foundFinish = true; continue; } FileSystem ns = fs.getVolumeByPath(child.getPath()).getFileSystem(); heap.add(new Index(new Reader(ns.makeQualified(child.getPath()), ns.getConf()))); } if (!foundFinish) throw new IOException("Sort \"" + SortedLogState.FINISHED.getMarker() + "\" flag not found in " + directory); } private static void copy(Writable src, Writable dest) throws IOException { // not exactly efficient... DataOutputBuffer output = new DataOutputBuffer(); src.write(output); DataInputBuffer input = new DataInputBuffer(); input.reset(output.getData(), output.getLength()); dest.readFields(input); } public synchronized boolean next(WritableComparable key, Writable val) throws IOException { Index elt = (Index) heap.remove(); try { elt.cache(); if (elt.cached) { copy(elt.key, key); copy(elt.value, val); elt.cached = false; } else { return false; } } finally { heap.add(elt); } return true; } public synchronized boolean seek(WritableComparable key) throws IOException { PriorityBuffer reheap = new PriorityBuffer(heap.size()); boolean result = false; for (Object obj : heap) { Index index = (Index) obj; try { WritableComparable found = index.reader.getClosest(key, index.value, true); if (found != null && found.equals(key)) { result = true; } } catch (EOFException ex) { // thrown if key is beyond all data in the map } index.cached = false; reheap.add(index); } heap = reheap; return result; } public void close() throws IOException { IOException problem = null; for (Object obj : heap) { Index index = (Index) obj; try { index.reader.close(); } catch (IOException ex) { problem = ex; } } if (problem != null) throw problem; heap = null; } }