/* * Copyright 2012 NGDATA nv * * 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.indexer.model.util; import javax.annotation.PreDestroy; import java.io.ByteArrayInputStream; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import com.ngdata.hbaseindexer.conf.IndexerComponentFactory; import com.ngdata.hbaseindexer.conf.IndexerComponentFactoryUtil; import com.ngdata.hbaseindexer.conf.IndexerConf; import com.ngdata.hbaseindexer.model.api.IndexerDefinition; import com.ngdata.hbaseindexer.model.api.IndexerModel; import com.ngdata.hbaseindexer.model.api.IndexerModelEvent; import com.ngdata.hbaseindexer.model.api.IndexerModelListener; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.lilyproject.indexer.model.indexerconf.IndexRecordFilter; import org.lilyproject.repository.api.QName; import org.lilyproject.repository.api.RepositoryManager; /** * See {@link IndexesInfo}. */ public class IndexesInfoImpl implements IndexesInfo { private final IndexerModel indexerModel; private final RepositoryManager repositoryManager; private Map<String, IndexInfo> indexInfos; private Set<QName> recordFilterFieldDependencies; private boolean recordFilterDependsOnRecordType; private final Listener listener = new Listener(); private final Log log = LogFactory.getLog(getClass()); private final ExecutorService executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1), new ThreadPoolExecutor.DiscardPolicy()); /** Has the initial load of the indexes been done? */ private volatile boolean initialized = false; public IndexesInfoImpl(IndexerModel indexerModel, RepositoryManager repositoryManager) { this.indexerModel = indexerModel; this.repositoryManager = repositoryManager; indexerModel.registerListener(listener); } @PreDestroy public void stop() { executor.shutdown(); } private synchronized void refresh() { Map<String, IndexInfo> newIndexInfos = new HashMap<String, IndexInfo>(); Collection<IndexerDefinition> indexDefs = indexerModel.getIndexers(); for (IndexerDefinition indexDef : indexDefs) { byte[] indexerConfXml = indexDef.getConfiguration(); IndexerConf indexerConf = null; try { // check if this is a lily index // FIXME: check on class name in a non-dependency module if (!"org.lilyproject.indexer.hbase.mapper.LilyIndexerComponentFactory".equals(indexDef.getIndexerComponentFactory())) { continue; } IndexerComponentFactory factory = IndexerComponentFactoryUtil.getComponentFactory(indexDef.getIndexerComponentFactory(), new ByteArrayInputStream(indexDef.getConfiguration()), indexDef.getConnectionParams()); factory.configure(new ByteArrayInputStream(indexerConfXml), indexDef.getConnectionParams()); indexerConf = factory.createIndexerConf(); // If parsing failed, we exclude the index if (indexerConf == null) { continue; } newIndexInfos.put(indexDef.getName(), new IndexInfo(indexDef, indexerConf, repositoryManager)); } catch (Throwable t) { log.error("Error parsing indexer conf", t); } } // Pre-calculate some cross-index information Set<QName> recordFilterFieldDependencies = new HashSet<QName>(); boolean recordFilterDependsOnRecordType = false; for (IndexInfo indexInfo : newIndexInfos.values()) { IndexRecordFilter recordFilter = indexInfo.getLilyIndexerConf().getRecordFilter(); recordFilterFieldDependencies.addAll(recordFilter.getFieldDependencies()); if (!recordFilterDependsOnRecordType) { recordFilterDependsOnRecordType = recordFilter.dependsOnRecordType(); } } this.indexInfos = newIndexInfos; this.recordFilterFieldDependencies = recordFilterFieldDependencies; this.recordFilterDependsOnRecordType = recordFilterDependsOnRecordType; } /** * Assures the indexes information is loaded on startup before the first information is consulted * from IndexesInfo. */ private void assureInitialized() { if (!initialized) { synchronized (this) { if (!initialized) { refresh(); initialized = true; } } } } @Override public Collection<IndexInfo> getIndexInfos() { assureInitialized(); return indexInfos.values(); } @Override public Set<QName> getRecordFilterFieldDependencies() { assureInitialized(); return recordFilterFieldDependencies; } @Override public boolean getRecordFilterDependsOnRecordType() { assureInitialized(); return recordFilterDependsOnRecordType; } private class Listener implements IndexerModelListener { @Override public void process(IndexerModelEvent event) { // The refresh is called asynchronously, in order not to block the delivery of // other events (cfr. single ZK event dispatch thread). // If there is still an outstanding refresh task waiting in the queue, we don't // need to add another one. The configuration of the ExecutorService takes care // of this (queue bounded to 1 item + discard policy). executor.submit(new Runnable() { @Override public void run() { refresh(); } }); } } }