/*
* 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.infinispan.AdvancedCache;
import org.infinispan.CacheException;
import org.infinispan.query.Transformable;
import org.infinispan.query.Transformer;
import org.infinispan.query.impl.ComponentRegistryUtils;
import org.infinispan.query.logging.Log;
import org.infinispan.util.Util;
import org.infinispan.util.concurrent.ConcurrentMapFactory;
import org.infinispan.util.logging.LogFactory;
import java.util.Map;
/**
* This transforms arbitrary keys to a String which can be used by Lucene as a document identifier, and vice versa.
* <p/>
* There are 2 approaches to doing so; one for SimpleKeys: Java primitives (and their object wrappers) and Strings, and
* one for custom, user-defined types that could be used as keys.
* <p/>
* For SimpleKeys, users don't need to do anything, these keys are automatically transformed by this class.
* <p/>
* For user-defined keys, two options are supported. Types annotated with @Transformable, and declaring an appropriate {@link
* org.infinispan.query.Transformer} implementation; and types for which a {@link org.infinispan.query.Transformer} has
* been explicitly registered through KeyTransformationHandler.registerTransformer().
*
* @author Manik Surtani
* @author Marko Luksa
* @see org.infinispan.query.Transformable
* @see org.infinispan.query.Transformer
* @since 4.0
*/
public class KeyTransformationHandler {
private static final Log log = LogFactory.getLog(KeyTransformationHandler.class, Log.class);
private final Map<Class<?>, Class<? extends Transformer>> transformerTypes = ConcurrentMapFactory.makeConcurrentMap();
public Object stringToKey(String s, ClassLoader classLoader) {
char type = s.charAt(0);
switch (type) {
case 'S':
// this is a normal String, but NOT a SHORT. For short see case 'x'.
return s.substring(2);
case 'I':
// This is an Integer
return Integer.parseInt(s.substring(2));
case 'Y':
// This is a BYTE
return Byte.parseByte(s.substring(2));
case 'L':
// This is a Long
return Long.parseLong(s.substring(2));
case 'X':
// This is a SHORT
return Short.parseShort(s.substring(2));
case 'D':
// This is a Double
return Double.parseDouble(s.substring(2));
case 'F':
// This is a Float
return Float.parseFloat(s.substring(2));
case 'B':
// This is a Boolean. This is NOT the case for a BYTE. For a BYTE, see case 'y'.
return Boolean.parseBoolean(s.substring(2));
case 'C':
// This is a Character
return s.charAt(2);
case 'T':
// this is a custom transformable.
int indexOfSecondDelimiter = s.indexOf(":", 2);
String keyClassName = s.substring(2, indexOfSecondDelimiter);
String keyAsString = s.substring(indexOfSecondDelimiter + 1);
Transformer t = getCustomTransformer(keyClassName, classLoader);
if (t == null) throw new CacheException("Cannot find an appropriate Transformer for key type " + keyClassName);
return t.fromString(keyAsString);
}
throw new CacheException("Unknown type metadata " + type);
}
private Transformer getCustomTransformer(final String keyClassName, final ClassLoader classLoader) {
Transformer t = null;
// try and locate class
Class<?> keyClass = null;
try {
keyClass = Util.loadClassStrict(keyClassName, classLoader);
} catch (ClassNotFoundException e) {
log.keyClassNotFound(keyClassName, e);
}
if (keyClass != null) {
t = getTransformer(keyClass);
}
return t;
}
public String keyToString(Object key) {
// this string should be in the format of
// "<TYPE>:(TRANSFORMER):<KEY>"
// e.g.:
// "S:my string key"
// "I:75"
// "D:5.34"
// "B:f"
// "T:com.myorg.MyTransformer:STRING_GENERATED_BY_MY_TRANSFORMER"
char prefix = ' ';
// First going to check if the key is a primitive or a String. Otherwise, check if it's a transformable.
// If none of those conditions are satisfied, we'll throw an Exception.
Transformer tf = null;
if (isStringOrPrimitive(key)) {
// Using 'X' for Shorts and 'Y' for Bytes because 'S' is used for Strings and 'B' is being used for Booleans.
if (key instanceof String)
prefix = 'S';
else if (key instanceof Integer)
prefix = 'I';
else if (key instanceof Boolean)
prefix = 'B';
else if (key instanceof Long)
prefix = 'L';
else if (key instanceof Float)
prefix = 'F';
else if (key instanceof Double)
prefix = 'D';
else if (key instanceof Short)
prefix = 'X';
else if (key instanceof Byte)
prefix = 'Y';
else if (key instanceof Character)
prefix = 'C';
return prefix + ":" + key;
} else if ((tf = getTransformer(key.getClass())) != null) {
// There is a bit more work to do for this case.
return "T:" + key.getClass().getName() + ":" + tf.toString(key);
} else
throw new IllegalArgumentException("Indexing only works with entries keyed on Strings, primitives " +
"and classes that have the @Transformable annotation - you passed in a " + key.getClass().toString() +
". Alternatively, see org.infinispan.query.SearchManager#registerKeyTransformer");
}
private boolean isStringOrPrimitive(Object key) {
// we support String and JDK primitives and their wrappers.
if (key instanceof String ||
key instanceof Integer ||
key instanceof Long ||
key instanceof Float ||
key instanceof Double ||
key instanceof Boolean ||
key instanceof Short ||
key instanceof Byte ||
key instanceof Character
)
return true;
return false;
}
/**
* Retrieves a {@link org.infinispan.query.Transformer} instance for this key. If the key is not
* {@link org.infinispan.query.Transformable} and no transformer has been registered for the key's class,
* null is returned.
*
* @param keyClass key class to analyze
* @return a Transformer for this key, or null if the key type is not properly annotated.
*/
private Transformer getTransformer(Class<?> keyClass) {
Class<?> transformerClass = getTransformerClass(keyClass);
if (transformerClass != null)
return instantiate(transformerClass);
return null;
}
private Class<? extends Transformer> getTransformerClass(Class<?> keyClass) {
Class<? extends Transformer> transformerClass = transformerTypes.get(keyClass);
if (transformerClass == null) {
transformerClass = getTransformerClassFromAnnotation(keyClass);
if (transformerClass != null)
registerTransformer(keyClass, transformerClass);
}
return transformerClass;
}
private Class<? extends Transformer> getTransformerClassFromAnnotation(Class<?> keyClass) {
Transformable annotation = keyClass.getAnnotation(Transformable.class);
if (annotation!=null) {
return annotation.transformer();
}
return null;
}
private Transformer instantiate(Class<?> transformerClass) {
try {
// The cast should not be necessary but it's a workaround for a compiler bug.
return (Transformer) transformerClass.newInstance();
} catch (Exception e) {
log.couldNotInstantiaterTransformerClass(transformerClass, e);
return null;
}
}
/**
* Registers a {@link org.infinispan.query.Transformer} for the supplied key class.
* @param keyClass the key class for which the supplied transformerClass should be used
* @param transformerClass the transformer class to use for the supplied key class
*/
public void registerTransformer(Class<?> keyClass, Class<? extends Transformer> transformerClass) {
transformerTypes.put(keyClass, transformerClass);
}
/**
* Gets the KeyTransformationHandler instance used by the supplied cache.
* @param cache the cache for which we want to get the KeyTransformationHandler instance
* @return a KeyTransformationHandler instance
*/
public static KeyTransformationHandler getInstance(AdvancedCache<?, ?> cache) {
QueryInterceptor queryInterceptor = ComponentRegistryUtils.getComponent(cache, QueryInterceptor.class);
return queryInterceptor.getKeyTransformationHandler();
}
}