Package org.infinispan.query.backend

Source Code of org.infinispan.query.backend.QueryInterceptor

/*
* JBoss, Home of Professional Open Source
* Copyright 2009 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.query.backend;

import org.hibernate.search.backend.TransactionContext;
import org.hibernate.search.backend.Work;
import org.hibernate.search.backend.WorkType;
import org.hibernate.search.spi.SearchFactoryIntegrator;
import org.hibernate.search.store.DirectoryProvider;
import org.infinispan.commands.write.ClearCommand;
import org.infinispan.commands.write.PutKeyValueCommand;
import org.infinispan.commands.write.PutMapCommand;
import org.infinispan.commands.write.RemoveCommand;
import org.infinispan.commands.write.ReplaceCommand;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.context.InvocationContext;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.interceptors.base.CommandInterceptor;
import org.infinispan.marshall.MarshalledValue;

import javax.transaction.TransactionManager;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static org.infinispan.query.backend.KeyTransformationHandler.keyToString;

/**
* This interceptor will be created when the System Property "infinispan.query.indexLocalOnly" is "false"
* <p/>
* This type of interceptor will allow the indexing of data even when it comes from other caches within a cluster.
* <p/>
* However, if the a cache would not be putting the data locally, the interceptor will not index it.
*
* @author Navin Surtani
* @author Sanne Grinovero <sanne@hibernate.org> (C) 2011 Red Hat Inc.
* @since 4.0
*/
public class QueryInterceptor extends CommandInterceptor {

   private final SearchFactoryIntegrator searchFactory;
   private final ConcurrentHashMap<Class,Class> knownClasses = new ConcurrentHashMap<Class,Class>();
   private final Lock mutating = new ReentrantLock();
   protected TransactionManager transactionManager;

   public QueryInterceptor(SearchFactoryIntegrator searchFactory) {
      this.searchFactory = searchFactory;
   }

   @Inject
   public void init(TransactionManager transactionManager) {
   }

   protected boolean shouldModifyIndexes(InvocationContext ctx) {
      return true;
   }

   @Override
   public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable {

      // This method will get the put() calls on the cache and then send them into Lucene once it's successful.
      // do the actual put first.
      Object toReturn = invokeNextInterceptor(ctx, command);

      if (shouldModifyIndexes(ctx)) {
         // First making a check to see if the key is already in the cache or not. If it isn't we can add the key no problem,
         // otherwise we need to be updating the indexes as opposed to simply adding to the indexes.
         Object key = command.getKey();
         Object value = extractValue(command.getValue());
         updateKnownTypesIfNeeded( value );
         CacheEntry entry = ctx.lookupEntry(key);

         // New entry so we will add it to the indexes.
         if(entry.isCreated()) {
            log.debug("Entry is created");
            addToIndexes(value, extractValue(key));
         }
         else{
            // This means that the entry is just modified so we need to update the indexes and not add to them.
            log.debug("Entry is changed");
            updateIndexes(value, extractValue(key));
         }

      }
      return toReturn;
   }

   @Override
   public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable {
      // remove the object out of the cache first.
      Object valueRemoved = invokeNextInterceptor(ctx, command);

      if (command.isSuccessful() && !command.isNonExistent() && shouldModifyIndexes(ctx)) {
         Object value = extractValue(valueRemoved);
         updateKnownTypesIfNeeded( value );
         removeFromIndexes( value, extractValue(command.getKey()));
      }
      return valueRemoved;
   }

   @Override
   public Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable {
      Object valueReplaced = invokeNextInterceptor(ctx, command);
      if (valueReplaced != null && shouldModifyIndexes(ctx)) {

         Object[] parameters = command.getParameters();
         Object p1 = extractValue(parameters[1]);
         Object p2 = extractValue(parameters[2]);
         updateKnownTypesIfNeeded( p1 );
         updateKnownTypesIfNeeded( p2 );
         Object key = extractValue(command.getKey());

         removeFromIndexes(p1, key);
         addToIndexes(p2, key);
      }

      return valueReplaced;
   }

   @Override
   public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable {
      Object mapPut = invokeNextInterceptor(ctx, command);

      if (shouldModifyIndexes(ctx)) {
         Map<Object, Object> dataMap = command.getMap();

         // Loop through all the keys and put those key, value pairings into lucene.

         for (Map.Entry entry : dataMap.entrySet()) {
            Object value = extractValue(entry.getValue());
            updateKnownTypesIfNeeded( value );
            addToIndexes(value, extractValue(entry.getKey()));
         }
      }
      return mapPut;
   }

   @Override
   public Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable {

      // This method is called when somebody calls a cache.clear() and we will need to wipe everything in the indexes.
      Object returnValue = invokeNextInterceptor(ctx, command);

      if (shouldModifyIndexes(ctx)) {
         if (trace) log.trace("shouldModifyIndexes() is true and we can clear the indexes");

         for (Class c : this.knownClasses.keySet()) {
            DirectoryProvider[] providers = this.searchFactory.getDirectoryProviders(c);
            Serializable id = null;
            if (providers != null && providers.length > 0) { //check as not all known classes are indexed
               searchFactory.getWorker().performWork(new Work<Object>(c, id, WorkType.PURGE_ALL),
                                                  new TransactionalEventTransactionContext(transactionManager));
            }
         }
      }
      return returnValue;
   }

   // Method that will be called when data needs to be added into Lucene.
   protected void addToIndexes(Object value, Object key) {
      if (trace) log.tracef("Adding to indexes for key [%s] and value [%s]", key, value);

      // The key here is the String representation of the key that is stored in the cache.
      // The key is going to be the documentID for Lucene.
      // The object parameter is the actual value that needs to be put into lucene.
      if (value == null) throw new NullPointerException("Cannot handle a null value!");
      TransactionContext transactionContext = new TransactionalEventTransactionContext(transactionManager);
      searchFactory.getWorker().performWork(new Work<Object>(value, keyToString(key), WorkType.ADD), transactionContext);
   }

   // Method that will be called when data needs to be removed from Lucene.
   protected void removeFromIndexes(Object value, Object key) {

      // The key here is the String representation of the key that is stored in the cache.
      // The key is going to be the documentID for Lucene.
      // The object parameter is the actual value that needs to be removed from lucene.
      if (value == null) throw new NullPointerException("Cannot handle a null value!");
      TransactionContext transactionContext = new TransactionalEventTransactionContext(transactionManager);
      searchFactory.getWorker().performWork(new Work<Object>(value, keyToString(key), WorkType.DELETE), transactionContext);
   }

   protected void updateIndexes(Object value, Object key){
      // The key here is the String representation of the key that is stored in the cache.
      // The key is going to be the documentID for Lucene.
      // The object parameter is the actual value that needs to be removed from lucene.
      if (value == null) throw new NullPointerException("Cannot handle a null value!");
      TransactionContext transactionContext = new TransactionalEventTransactionContext(transactionManager);
      searchFactory.getWorker().performWork(new Work<Object>(value, keyToString(key), WorkType.UPDATE), transactionContext);
   }

   private Object extractValue(Object wrappedValue) {
      if (wrappedValue instanceof MarshalledValue)
         return ((MarshalledValue) wrappedValue).get();
      else
         return wrappedValue;
   }

   public void enableClasses(Class<?>[] classes) {
      if ( classes == null || classes.length == 0 ) {
         return;
      }
      enableClassesIncrementally(classes, false);
   }
  
   private void enableClassesIncrementally(Class<?>[] classes, boolean locked) {
      ArrayList<Class> toAdd = null;
      for (Class type : classes) {
         if (knownClasses.contains(type) == false) {
            if (toAdd==null)
               toAdd = new ArrayList<Class>(classes.length);
            toAdd.add(type);
         }
      }
      if (toAdd == null) {
         return;
      }
      if (locked) {
         Set<Class> existingClasses = knownClasses.keySet();
         int index = existingClasses.size();
         Class[] all = (Class[]) existingClasses.toArray(new Class[existingClasses.size()+toAdd.size()]);
         for (Class toAddClass : toAdd) {
            all[index++] = toAddClass;
         }
         searchFactory.addClasses(all);
         for (Class type : toAdd) {
            knownClasses.put(type, type);
         }
      } else {
         mutating.lock();
         try {
            enableClassesIncrementally(classes, true);
         } finally {
            mutating.unlock();
         }
      }
   }
  
   private void updateKnownTypesIfNeeded(Object value) {
      if ( value != null ) {
         Class<? extends Object> potentialNewType = value.getClass();
         if ( ! this.knownClasses.contains(potentialNewType) ) {
            mutating.lock();
            try {
               enableClassesIncrementally( new Class[]{potentialNewType}, true);
            }
            finally {
               mutating.unlock();
            }
         }
      }
   }
}
TOP

Related Classes of org.infinispan.query.backend.QueryInterceptor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.