/* * Copyright 2010 Outerthought bvba * * 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.lilyproject.hbaseindex; import java.io.ByteArrayOutputStream; import java.io.IOException; import org.lilyproject.util.hbase.RepoAndTableUtil; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.client.HTableInterface; import org.apache.hadoop.hbase.util.Bytes; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.node.ObjectNode; import org.lilyproject.util.ObjectUtils; import org.lilyproject.util.hbase.HBaseTableFactory; import org.lilyproject.util.hbase.HBaseTableFactoryImpl; import org.lilyproject.util.hbase.LocalHTable; import org.lilyproject.util.io.Closer; /** * Starting point for all the index and query functionality. * * <p>This class should be instantiated yourself. This class is threadsafe, * but on the other hand rather lightweight so it does not harm to have multiple * instances. */ public class IndexManager { private Configuration hbaseConf; private HBaseTableFactory tableFactory; private static final byte[] INDEX_META_KEY = Bytes.toBytes("LILY_INDEX"); /** * Constructor. */ public IndexManager(Configuration hbaseConf) throws IOException { this(hbaseConf, null); } public IndexManager(Configuration hbaseConf, HBaseTableFactory tableFactory) throws IOException { this.hbaseConf = hbaseConf; this.tableFactory = tableFactory != null ? tableFactory : new HBaseTableFactoryImpl(hbaseConf); } /** * Creates a new index in the default repository. * * @param indexDef definition of the index to be created * @throws InterruptedException * @throws IOException * * @throws IndexExistsException if an index with the same name already exists * @throws IndexNotFoundException very unlikely to occur here * @deprecated Use the version of this method where a repository name is supplied */ @Deprecated public synchronized Index getIndex(IndexDefinition indexDef) throws IOException, InterruptedException, IndexNotFoundException { return getIndex(RepoAndTableUtil.DEFAULT_REPOSITORY, indexDef); } /** * Creates a new index. * * @param repositoryName name of the owning repository * @param indexDef definition of the index to be created * * @throws IndexExistsException if an index with the same name already exists * @throws IndexNotFoundException very unlikely to occur here */ public synchronized Index getIndex(String repositoryName, IndexDefinition indexDef) throws IOException, InterruptedException, IndexNotFoundException { if (indexDef.getFields().size() == 0) { throw new IllegalArgumentException("An IndexDefinition should contain at least one field."); } byte[] jsonData = serialize(indexDef); HTableDescriptor tableDescr = new HTableDescriptor(indexDef.getName()); HColumnDescriptor family = new HColumnDescriptor(IndexDefinition.DATA_FAMILY, 1, HColumnDescriptor.DEFAULT_COMPRESSION, HColumnDescriptor.DEFAULT_IN_MEMORY, HColumnDescriptor.DEFAULT_BLOCKCACHE, HColumnDescriptor.DEFAULT_BLOCKSIZE, HColumnDescriptor.DEFAULT_TTL, HColumnDescriptor.DEFAULT_BLOOMFILTER, HColumnDescriptor.DEFAULT_REPLICATION_SCOPE); tableDescr.addFamily(family); // Store definition of index in a custom attribute on the table tableDescr.setValue(INDEX_META_KEY, jsonData); RepoAndTableUtil.setRepositoryOwnership(tableDescr, repositoryName); HTableInterface table = tableFactory.getTable(tableDescr); byte[] actualMeta = table.getTableDescriptor().getValue(INDEX_META_KEY); if (!ObjectUtils.safeEquals(jsonData, actualMeta)) { throw new RuntimeException("Index " + indexDef.getName() + " exists but its definition does not match the supplied definition."); } return instantiateIndex(indexDef.getName(), table); } private byte[] serialize(IndexDefinition indexDef) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(os, indexDef.toJson()); return os.toByteArray(); } private IndexDefinition deserialize(String name, byte[] jsonData) throws IOException { ObjectMapper mapper = new ObjectMapper(); return new IndexDefinition(name, mapper.readValue(jsonData, 0, jsonData.length, ObjectNode.class)); } /** * Retrieves an Index. * * @throws IndexNotFoundException if the index does not exist */ public Index getIndex(String name) throws IOException, IndexNotFoundException { HTableInterface table; try { table = new LocalHTable(hbaseConf, name); } catch (RuntimeException e) { if (e.getCause() != null && e.getCause() instanceof TableNotFoundException) { throw new IndexNotFoundException(name); } else { throw e; } } catch (TableNotFoundException e) { throw new IndexNotFoundException(name); } return instantiateIndex(name, table); } private Index instantiateIndex(String name, HTableInterface table) throws IOException, IndexNotFoundException { byte[] jsonData; try { jsonData = table.getTableDescriptor().getValue(INDEX_META_KEY); } catch (RuntimeException e) { if (e.getCause() != null && e.getCause() instanceof TableNotFoundException) { throw new IndexNotFoundException(name); } else { throw e; } } catch (TableNotFoundException e) { throw new IndexNotFoundException(name); } if (jsonData == null) { throw new IOException("Not a valid index table: " + name); } IndexDefinition indexDef = deserialize(name, jsonData); return new Index(table, indexDef); } /** * Deletes an index. * * <p>This disables the index table and deletes it. * * @throws IndexNotFoundException if the index does not exist. */ public synchronized void deleteIndex(String name) throws IOException, IndexNotFoundException { HBaseAdmin hbaseAdmin = new HBaseAdmin(hbaseConf); try { HTableDescriptor tableDescr; try { tableDescr = hbaseAdmin.getTableDescriptor(Bytes.toBytes(name)); } catch (RuntimeException e) { if (e.getCause() != null && e.getCause() instanceof TableNotFoundException) { throw new IndexNotFoundException(name); } else { throw e; } } catch (TableNotFoundException e) { throw new IndexNotFoundException(name); } if (tableDescr.getValue(INDEX_META_KEY) == null) { throw new IOException("Table exists but is not an index table: " + name); } hbaseAdmin.disableTable(name); hbaseAdmin.deleteTable(name); } finally { Closer.close(hbaseAdmin); } } }