/** * Copyright 2009 the original author or authors. * * 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 net.sf.katta.lib.mapfile; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import net.sf.katta.node.IContentServer; import net.sf.katta.util.NodeConfiguration; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.io.MapFile; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.Writable; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.MapFile.Reader; import org.apache.log4j.Logger; /** * Implements search over a set of Hadoop <code>MapFile</code>s. */ public class MapFileServer implements IContentServer, IMapFileServer { private final static Logger LOG = Logger.getLogger(MapFileServer.class); private final Configuration _conf = new Configuration(); private final FileSystem _fileSystem; private final Map<String, MapFile.Reader> _readerByShard = new ConcurrentHashMap<String, MapFile.Reader>(); private String _nodeName; public MapFileServer() throws IOException { _fileSystem = FileSystem.getLocal(_conf); } public long getProtocolVersion(final String protocol, final long clientVersion) throws IOException { return 0L; } @Override public void init(String nodeName, NodeConfiguration nodeConfiguration) { _nodeName = nodeName; } /** * Adds an shard index search for given name to the list of shards * MultiSearcher search in. * * @param shardName * @throws IOException */ public void addShard(final String shardName, final File shardDir) throws IOException { LOG.debug("LuceneServer " + _nodeName + " got shard " + shardName); if (!shardDir.exists()) { throw new IOException("Shard " + shardName + " dir " + shardDir.getAbsolutePath() + " does not exist!"); } if (!shardDir.canRead()) { throw new IOException("Can not read shard " + shardName + " dir " + shardDir.getAbsolutePath() + "!"); } try { final MapFile.Reader reader = new MapFile.Reader(_fileSystem, shardDir.getAbsolutePath(), _conf); synchronized (_readerByShard) { _readerByShard.put(shardName, reader); } } catch (IOException e) { LOG.error("Error opening shard " + shardName + " " + shardDir.getAbsolutePath(), e); throw e; } } @Override public Collection<String> getShards() { return Collections.unmodifiableCollection(_readerByShard.keySet()); } /** * * Removes a search by given shardName from the list of searchers. */ public void removeShard(final String shardName) throws IOException { LOG.debug("LuceneServer " + _nodeName + " removing shard " + shardName); synchronized (_readerByShard) { final MapFile.Reader reader = _readerByShard.get(shardName); if (reader != null) { try { reader.close(); } catch (IOException e) { LOG.error("Error closing shard " + shardName, e); throw e; } _readerByShard.remove(shardName); } else { LOG.warn("Shard " + shardName + " not found!"); } } } /** * Returns data about a shard. Currently the only standard key is * SHARD_SIZE_KEY. This value will be reported by the listIndexes command. The * units depend on the type of server. It is OK to return an empty map or * null. * * @param shardName * The name of the shard to measure. This was the name provided in * addShard(). * @return a map of key/value pairs which describe the shard. * @throws Exception */ public Map<String, String> getShardMetaData(String shardName) throws Exception { final MapFile.Reader reader = _readerByShard.get(shardName); if (reader != null) { int count = 0; synchronized (reader) { reader.reset(); WritableComparable<?> key = (WritableComparable<?>) reader.getKeyClass().newInstance(); Writable value = (Writable) reader.getValueClass().newInstance(); while (reader.next(key, value)) { count++; } } Map<String, String> metaData = new HashMap<String, String>(); metaData.put(SHARD_SIZE_KEY, Integer.toString(count)); return metaData; } LOG.warn("Shard " + shardName + " not found!"); throw new IllegalArgumentException("Shard " + shardName + " unknown"); } /** * Close all MapFiles. No further calls will be made after this one. */ public void shutdown() throws IOException { for (final MapFile.Reader reader : _readerByShard.values()) { try { reader.close(); } catch (IOException e) { LOG.error("Error in shutdown", e); } } _readerByShard.clear(); } public TextArrayWritable get(Text key, String[] shards) throws IOException { ExecutorService executor = Executors.newCachedThreadPool(); Collection<Future<Text>> futures = new ArrayList<Future<Text>>(); for (String shard : shards) { final MapFile.Reader reader = _readerByShard.get(shard); if (reader == null) { LOG.warn("Shard " + shard + " unknown"); continue; } Callable<Text> callable = new MapLookup(reader, key); futures.add(executor.submit(callable)); } executor.shutdown(); try { executor.awaitTermination(1, TimeUnit.MINUTES); // TODO: config, 10 sec? } catch (InterruptedException e) { LOG.warn("Interrupted while waiting on MapLookup threads", e); } executor.shutdownNow(); List<Text> resultList = new ArrayList<Text>(); for (Future<Text> future : futures) { try { Text result = future.get(0, TimeUnit.MILLISECONDS); if (result != null) { resultList.add(result); } } catch (ExecutionException e) { /* * This MapFile red threw an exception. Stop processing and throw an * IOE. */ Throwable t = e.getCause(); if (t instanceof IOException) { // Throw the same IOException that the MapFile.Reader threw. throw (IOException) t; } // Wrap MapFile.Reader's exception in an IOException. throw new IOException("Error in MapLookup", t); } catch (TimeoutException e) { /* * Result is not ready. Should not happen, because future is done. * Continue as if MapLookup had returned null. */ LOG.warn("Timed out while getting MapLookup", e); } catch (InterruptedException e) { /* * Something went wrong while waiting for result. Should not happen * because we wait for 0 msec, and the future is done. Continue as if * the MapLookup had returned null. */ LOG.warn("Interrupted while getting RPC result", e); } } return new TextArrayWritable(resultList); } private class MapLookup implements Callable<Text> { private MapFile.Reader _reader; private WritableComparable<?> _key; public MapLookup(Reader reader, WritableComparable<?> key) { _reader = reader; _key = key; } public Text call() throws Exception { synchronized (_reader) { Writable result = (Writable) _reader.getValueClass().newInstance(); result = _reader.get(_key, result); return (Text) result; } } } }