Clover coverage report -
Coverage timestamp: Sun Nov 1 2009 23:08:24 UTC
file stats: LOC: 483   Methods: 18
NCLOC: 336   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
IndexManager.java 61.4% 75.1% 94.4% 72%
coverage coverage
 1    /*
 2    * Licensed to the Apache Software Foundation (ASF) under one or more
 3    * contributor license agreements. See the NOTICE file distributed with
 4    * this work for additional information regarding copyright ownership.
 5    * The ASF licenses this file to You under the Apache License, Version 2.0
 6    * (the "License"); you may not use this file except in compliance with
 7    * the License. You may obtain a copy of the License at
 8    *
 9    * http://www.apache.org/licenses/LICENSE-2.0
 10    *
 11    * Unless required by applicable law or agreed to in writing, software
 12    * distributed under the License is distributed on an "AS IS" BASIS,
 13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14    * See the License for the specific language governing permissions and
 15    * limitations under the License.
 16    *
 17    * $Id: IndexManager.java 712561 2008-11-09 21:48:59Z vgritsenko $
 18    */
 19   
 20    package org.apache.xindice.core.indexer;
 21   
 22    import org.apache.commons.logging.Log;
 23    import org.apache.commons.logging.LogFactory;
 24    import org.apache.xindice.Stopwatch;
 25    import org.apache.xindice.core.Collection;
 26    import org.apache.xindice.core.DBException;
 27    import org.apache.xindice.core.data.Entry;
 28    import org.apache.xindice.core.data.Key;
 29    import org.apache.xindice.core.data.RecordSet;
 30    import org.apache.xindice.util.Configuration;
 31    import org.apache.xindice.util.ConfigurationCallback;
 32    import org.apache.xindice.util.SimpleConfigurable;
 33    import org.apache.xindice.util.XindiceException;
 34    import org.apache.xindice.xml.SymbolTable;
 35   
 36    import org.w3c.dom.Document;
 37   
 38    import java.util.ArrayList;
 39    import java.util.HashMap;
 40    import java.util.Iterator;
 41    import java.util.List;
 42    import java.util.Map;
 43    import java.util.Timer;
 44    import java.util.TimerTask;
 45    import java.util.WeakHashMap;
 46   
 47    /**
 48    * IndexManager is a class that manages Indexes. Good description, eh?
 49    * I should win a Pulitzer Prize for that one.
 50    *
 51    * @version $Revision: 712561 $, $Date: 2008-11-09 21:48:59 +0000 (Sun, 09 Nov 2008) $
 52    */
 53    public final class IndexManager extends SimpleConfigurable {
 54   
 55    private static final Log log = LogFactory.getLog(IndexManager.class);
 56   
 57    private static final String[] EMPTY_STRINGS = new String[0];
 58    private static final Indexer[] EMPTY_INDEXERS = new Indexer[0];
 59   
 60    private static final String INDEX = "index";
 61    private static final String NAME = "name";
 62    private static final String CLASS = "class";
 63   
 64    private static final Integer STATUS_READY = new Integer(0);
 65    private static final Integer STATUS_BUSY = new Integer(1);
 66   
 67    private Map indexes = new HashMap(); // Name to Indexer
 68    private Map status = new HashMap(); // Name to Status
 69    private Map bestIndexers = new HashMap(); // String to Map of IndexPattern to Indexer
 70   
 71    private Collection collection;
 72    private Timer timer;
 73    private SymbolTable symbols;
 74    private final List newIndexers = new ArrayList(); // of Indexers
 75   
 76    private int taskCount; // counter of scheduled tasks
 77    private final Object lock = new Object(); // lock object for manipulating taskCounter
 78   
 79    /**
 80    * Create IndexManager for a given collection
 81    *
 82    * @param collection Collection for this IndexManager
 83    * @param timer Timer for indexing tasks
 84    */
 85  1281 public IndexManager(Collection collection, Timer timer) {
 86  1281 this.collection = collection;
 87  1281 this.symbols = collection.getSymbols();
 88  1281 this.timer = timer;
 89    }
 90   
 91    /**
 92    * Configure index manager, register all indexes specified in the configuration
 93    *
 94    * @param config IndexManager configuration
 95    */
 96  1281 public void setConfig(Configuration config) throws XindiceException {
 97  1281 super.setConfig(config);
 98   
 99  1281 config.processChildren(INDEX, new ConfigurationCallback() {
 100  0 public void process(Configuration cfg) {
 101  0 String className = cfg.getAttribute(CLASS);
 102  0 try {
 103  0 register(Class.forName(className), cfg);
 104    } catch (Exception e) {
 105  0 if (log.isWarnEnabled()) {
 106  0 log.warn("Failed to register index with class '" + className + "' for collection '" + collection.getCanonicalName() + "'", e);
 107    }
 108    }
 109    }
 110    });
 111    }
 112   
 113    /**
 114    * list returns a list of the Indexers that this IndexerManager has
 115    * registered.
 116    *
 117    * @return An array containing the Indexer names
 118    */
 119  48 public synchronized String[] list() {
 120  48 return (String[]) indexes.keySet().toArray(EMPTY_STRINGS);
 121    }
 122   
 123    /**
 124    * drop physically removes the specified Indexer and any
 125    * associated system resources that the Indexer uses.
 126    *
 127    * @param name The Indexer to drop
 128    * @return Whether or not the Indexer was dropped
 129    */
 130  144 public synchronized boolean drop(final String name) {
 131    // Get indexer
 132  144 Indexer idx = get(name);
 133   
 134    // Unregister and remove from coniguration
 135  144 unregister(name);
 136  144 config.processChildren(INDEX, new ConfigurationCallback() {
 137  148 public void process(Configuration cfg) {
 138  148 try {
 139  148 if (cfg.getAttribute(NAME).equals(name)) {
 140  144 cfg.delete();
 141    }
 142    } catch (Exception e) {
 143  0 if (log.isWarnEnabled()) {
 144  0 log.warn("ignored exception", e);
 145    }
 146    }
 147    }
 148    });
 149   
 150    // Drop indexer
 151  144 boolean res = false;
 152  144 try {
 153  144 res = idx.drop();
 154    } catch (Exception e) {
 155  0 if (log.isWarnEnabled()) {
 156  0 log.warn("ignored exception", e);
 157    }
 158    }
 159  144 return res;
 160    }
 161   
 162    /**
 163    * Drop all indexers
 164    */
 165  1036 public synchronized void drop() {
 166  1036 String[] names = (String[]) indexes.keySet().toArray(new String[0]);
 167   
 168    // Drop indexes
 169  1036 for (int i = 0; i < names.length; i++) {
 170  86 drop(names[i]);
 171    }
 172    }
 173   
 174    /**
 175    * create creates a new Indexer object and any associated
 176    * system resources that the Indexer will need.
 177    *
 178    * @param cfg The Indexer's configuration
 179    * @return The Indexer that was created
 180    * @throws DBException if unable to create specified indexer
 181    */
 182  154 public synchronized Indexer create(Configuration cfg) throws DBException {
 183  154 if (!INDEX.equals(cfg.getName())) {
 184  0 throw new CannotCreateException("Cannot create index in " + collection.getCanonicalName() +
 185    ". Index configuration top element must be 'index'");
 186    }
 187   
 188  154 String name = cfg.getAttribute(NAME);
 189  154 try {
 190    // Check for duplicates
 191  154 Configuration[] cfgs = config.getChildren();
 192  154 for (int i = 0; i < cfgs.length; i++) {
 193  8 if (cfgs[i].getAttribute(NAME).equals(name)) {
 194  3 throw new DuplicateIndexException("Duplicate Index '" + name + "' in collection '" + collection.getCanonicalName() + "'");
 195    }
 196    }
 197   
 198  151 String className = cfg.getAttribute(CLASS);
 199  151 Indexer idx = register(Class.forName(className), cfg);
 200  144 config.add(cfg);
 201   
 202  144 return idx;
 203    } catch (DBException e) {
 204  7 throw e;
 205    } catch (Exception e) {
 206  3 throw new CannotCreateException("Cannot create index '" + name + "' in " + collection.getCanonicalName(), e);
 207    }
 208    }
 209   
 210    /**
 211    * Closes all indexers managed by this index manager.
 212    */
 213  246 public synchronized void close() {
 214    // wait for all scheduled tasks to finish
 215  246 synchronized (lock) {
 216  246 while (taskCount > 0) {
 217  0 try {
 218  0 lock.wait();
 219    } catch (InterruptedException e) {
 220    // ignore
 221    }
 222    }
 223    }
 224   
 225    // close all indexers
 226  246 for (Iterator i = indexes.values().iterator(); i.hasNext(); ) {
 227  0 Indexer idx = (Indexer) i.next();
 228  0 try {
 229  0 idx.close();
 230    } catch (DBException e) {
 231  0 if (log.isWarnEnabled()) {
 232  0 log.warn("Failed to close indexer " + idx.getName() + " on collection " + collection.getCanonicalName(), e);
 233    }
 234    }
 235    }
 236    }
 237   
 238  148 public synchronized Indexer register(Class c, Configuration cfg) throws DBException {
 239  148 String name = null;
 240  148 try {
 241  148 Indexer idx = (Indexer) c.newInstance();
 242  148 initialize(idx, cfg);
 243   
 244  148 name = idx.getName();
 245  148 if (name == null || name.trim().equals("")) {
 246  3 throw new CannotCreateException("No name specified");
 247    }
 248   
 249  145 if (!idx.exists()) {
 250  145 idx.create();
 251  144 idx.open();
 252   
 253  144 status.put(name, STATUS_BUSY);
 254  144 synchronized (newIndexers) {
 255  144 newIndexers.add(idx);
 256    }
 257   
 258  144 synchronized (lock) {
 259  144 taskCount++;
 260  144 try {
 261    // Schedule new task
 262  144 timer.schedule(new PopulateIndexersTimerTask(), 0);
 263    } catch (RuntimeException e) {
 264    // If failed to schedule the task, decrease the counter.
 265  0 taskCount--;
 266  0 throw e;
 267    } catch (Error e) {
 268    // If failed to schedule the task, decrease the counter.
 269  0 taskCount--;
 270  0 throw e;
 271    }
 272   
 273  144 if (log.isDebugEnabled()) {
 274  0 log.debug("Scheduled new task, count is " + taskCount);
 275    }
 276    }
 277    } else {
 278  0 status.put(name, STATUS_READY);
 279  0 idx.open();
 280    }
 281  144 indexes.put(name, idx);
 282   
 283  144 Map tbl = (Map) bestIndexers.get(idx.getIndexStyle());
 284  144 if (tbl != null) {
 285  42 tbl.clear();
 286    }
 287   
 288  144 return idx;
 289    } catch (DBException e) {
 290  4 throw e;
 291    } catch (Exception e) {
 292  0 throw new CannotCreateException("Cannot create Index '" + name + "' in " + collection.getCanonicalName(), e);
 293    }
 294    }
 295   
 296  144 public synchronized void unregister(String name) {
 297  144 Indexer idx = (Indexer) indexes.remove(name);
 298  144 status.remove(name);
 299   
 300  144 String style = idx.getIndexStyle();
 301  144 Map tbl = (Map) bestIndexers.get(style);
 302  144 if (tbl != null) {
 303  70 tbl.clear();
 304    }
 305    }
 306   
 307  148 private void initialize(Indexer idx, Configuration cfg) throws XindiceException {
 308  148 idx.setCollection(collection);
 309  148 idx.setConfig(cfg);
 310    }
 311   
 312  144 private void populateNewIndexers() throws DBException {
 313  144 Indexer[] list;
 314  144 synchronized (newIndexers) {
 315  144 list = (Indexer[]) newIndexers.toArray(EMPTY_INDEXERS);
 316  144 newIndexers.clear();
 317    }
 318   
 319  144 if (list.length > 0) {
 320  144 if (log.isTraceEnabled()) {
 321  0 for (int i = 0; i < list.length; i++) {
 322  0 log.trace("Index Creation: " + list[i].getName());
 323    }
 324    }
 325   
 326  144 Stopwatch sw = null;
 327  144 if (log.isDebugEnabled()) {
 328  0 sw = new Stopwatch("Populated Indexes", true);
 329    }
 330   
 331  144 RecordSet rs = collection.getFiler().getRecordSet();
 332  144 while (rs.hasMoreRecords()) {
 333    // Read only key, we don't need filer-level value
 334  10597 Key key = rs.getNextKey();
 335  10597 Entry entry = collection.getEntry(key);
 336  10597 if (entry != null && entry.getEntryType() == Entry.DOCUMENT) {
 337  10597 try {
 338  10597 new DocumentHandler(symbols, key, (Document) entry.getValue(), DocumentHandler.ACTION_CREATE, list);
 339    } catch (Exception e) {
 340  0 if (log.isWarnEnabled()) {
 341  0 log.warn("Failed to index document " + key, e);
 342    }
 343    }
 344    }
 345    }
 346   
 347  144 for (int i = 0; i < list.length; i++) {
 348  144 try {
 349  144 list[i].flush();
 350    } catch (Exception e) {
 351  0 if (log.isWarnEnabled()) {
 352  0 log.warn("ignored exception", e);
 353    }
 354    }
 355  144 status.put(list[i].getName(), STATUS_READY);
 356    }
 357   
 358  144 if (log.isDebugEnabled()) {
 359  0 sw.stop();
 360  0 for (int i = 0; i < list.length; i++) {
 361  0 log.debug("Index Complete: " + list[i].getName());
 362    }
 363  0 log.debug(sw.toString());
 364    }
 365    }
 366    }
 367   
 368    /**
 369    * get retrieves an Indexer by name.
 370    *
 371    * @param name The Indexer name
 372    * @return The Indexer
 373    */
 374  210 public synchronized Indexer get(String name) {
 375  210 return (Indexer) indexes.get(name);
 376    }
 377   
 378    /**
 379    * getBestIndexer retrieves the best Indexer to use for the specified
 380    * IndexPattern.
 381    *
 382    * @param style The Indexer Style (ex: Node, Value)
 383    * @param pattern The IndexPattern to use
 384    * @return The best Indexer (or null)
 385    */
 386  1099 public Indexer getBestIndexer(String style, IndexPattern pattern) {
 387  1099 Map tbl = (Map) bestIndexers.get(style);
 388  1099 if (tbl == null) {
 389  160 tbl = new WeakHashMap(); // FIXME: Review usage of WeakHashMap
 390  160 bestIndexers.put(style, tbl);
 391    }
 392   
 393  1099 Indexer idx = (Indexer) tbl.get(pattern);
 394  1099 if (idx == null) {
 395  1020 int highScore = 0;
 396  1020 Iterator i = indexes.values().iterator();
 397  1020 while (i.hasNext()) {
 398  232 Indexer index = (Indexer) i.next();
 399    // Indexer is not ready: can not use it
 400  232 if (!status.get(index.getName()).equals(STATUS_READY)) {
 401  2 continue;
 402    }
 403   
 404    // Indexer is of different style: can not use it
 405  230 if (!index.getIndexStyle().equals(style)) {
 406  107 continue;
 407    }
 408   
 409    // TODO: should it check patterns?
 410    // there can be only one full text index for a collection
 411  123 if (style.equals(Indexer.STYLE_FULLTEXT)) {
 412  34 return index;
 413    }
 414   
 415  89 for (int j = 0; j < index.getPatterns().length; j++) {
 416  89 int score = pattern.getMatchLevel(index.getPatterns()[j]);
 417   
 418  89 if (score > highScore) {
 419  41 idx = index;
 420  41 highScore = score;
 421    }
 422    }
 423   
 424    }
 425  986 tbl.put(pattern, idx);
 426    }
 427  1065 return idx;
 428    }
 429   
 430  10196 public void addDocument(Key key, Document doc) {
 431  10196 if (indexes.size() > 0) {
 432  374 Indexer[] idxList = (Indexer[]) indexes.values().toArray(EMPTY_INDEXERS);
 433  374 new DocumentHandler(symbols, key, doc, DocumentHandler.ACTION_CREATE, idxList);
 434  374 for (int i = 0; i < idxList.length; i++) {
 435  384 try {
 436  384 idxList[i].flush();
 437    } catch (Exception e) {
 438  0 if (log.isWarnEnabled()) {
 439  0 log.warn("ignored exception", e);
 440    }
 441    }
 442    }
 443    }
 444    }
 445   
 446  6700 public void removeDocument(Key key, Document doc) {
 447  6700 if (indexes.size() > 0) {
 448  129 Indexer[] idxList = (Indexer[]) indexes.values().toArray(EMPTY_INDEXERS);
 449  129 new DocumentHandler(symbols, key, doc, DocumentHandler.ACTION_DELETE, idxList);
 450  129 for (int i = 0; i < idxList.length; i++) {
 451  129 try {
 452  129 idxList[i].flush();
 453    } catch (Exception e) {
 454  0 if (log.isWarnEnabled()) {
 455  0 log.warn("ignored exception", e);
 456    }
 457    }
 458    }
 459    }
 460    }
 461   
 462    private class PopulateIndexersTimerTask extends TimerTask {
 463  144 public void run() {
 464  144 try {
 465  144 populateNewIndexers();
 466    } catch (DBException e) {
 467  0 if (log.isWarnEnabled()) {
 468  0 log.warn("ignored exception", e);
 469    }
 470    } finally {
 471  144 synchronized (lock) {
 472  144 taskCount--;
 473  144 if (log.isDebugEnabled()) {
 474  0 log.debug("Task completed, count is " + taskCount);
 475    }
 476  144 if (taskCount == 0) {
 477  144 lock.notifyAll();
 478    }
 479    }
 480    }
 481    }
 482    }
 483    }