| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.solr.client.solrj.beans; |
| |
| import org.apache.solr.common.SolrDocumentList; |
| import org.apache.solr.common.SolrDocument; |
| import org.apache.solr.common.SolrInputDocument; |
| |
| import java.lang.reflect.*; |
| import java.util.*; |
| import java.util.regex.Pattern; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.nio.ByteBuffer; |
| |
| /** |
| * A class to map objects to and from solr documents. |
| * |
| * |
| * @since solr 1.3 |
| */ |
| public class DocumentObjectBinder { |
| |
| private final Map<Class, List<DocField>> infocache = new ConcurrentHashMap<>(); |
| |
| public DocumentObjectBinder() { |
| } |
| |
| public <T> List<T> getBeans(Class<T> clazz, SolrDocumentList solrDocList) { |
| List<DocField> fields = getDocFields(clazz); |
| List<T> result = new ArrayList<>(solrDocList.size()); |
| |
| for (SolrDocument sdoc : solrDocList) { |
| result.add(getBean(clazz, fields, sdoc)); |
| } |
| return result; |
| } |
| |
| public <T> T getBean(Class<T> clazz, SolrDocument solrDoc) { |
| return getBean(clazz, null, solrDoc); |
| } |
| |
| private <T> T getBean(Class<T> clazz, List<DocField> fields, SolrDocument solrDoc) { |
| if (fields == null) { |
| fields = getDocFields(clazz); |
| } |
| |
| try { |
| T obj = clazz.newInstance(); |
| for (DocField docField : fields) { |
| docField.inject(obj, solrDoc); |
| } |
| return obj; |
| } catch (Exception e) { |
| throw new BindingException("Could not instantiate object of " + clazz, e); |
| } |
| } |
| |
| public SolrInputDocument toSolrInputDocument(Object obj) { |
| List<DocField> fields = getDocFields(obj.getClass()); |
| if (fields.isEmpty()) { |
| throw new BindingException("class: " + obj.getClass() + " does not define any fields."); |
| } |
| |
| SolrInputDocument doc = new SolrInputDocument(); |
| for (DocField field : fields) { |
| if (field.dynamicFieldNamePatternMatcher != null && |
| field.get(obj) != null && |
| field.isContainedInMap) { |
| Map<String, Object> mapValue = (Map<String, Object>) field.get(obj); |
| |
| for (Map.Entry<String, Object> e : mapValue.entrySet()) { |
| doc.setField(e.getKey(), e.getValue(), 1.0f); |
| } |
| } else { |
| doc.setField(field.name, field.get(obj), 1.0f); |
| } |
| } |
| return doc; |
| } |
| |
| private List<DocField> getDocFields(Class clazz) { |
| List<DocField> fields = infocache.get(clazz); |
| if (fields == null) { |
| synchronized(infocache) { |
| infocache.put(clazz, fields = collectInfo(clazz)); |
| } |
| } |
| return fields; |
| } |
| |
| private List<DocField> collectInfo(Class clazz) { |
| List<DocField> fields = new ArrayList<>(); |
| Class superClazz = clazz; |
| List<AccessibleObject> members = new ArrayList<>(); |
| |
| while (superClazz != null && superClazz != Object.class) { |
| members.addAll(Arrays.asList(superClazz.getDeclaredFields())); |
| members.addAll(Arrays.asList(superClazz.getDeclaredMethods())); |
| superClazz = superClazz.getSuperclass(); |
| } |
| |
| for (AccessibleObject member : members) { |
| if (member.isAnnotationPresent(Field.class)) { |
| member.setAccessible(true); |
| fields.add(new DocField(member)); |
| } |
| } |
| return fields; |
| } |
| |
| private static class DocField { |
| private String name; |
| private java.lang.reflect.Field field; |
| private Method setter; |
| private Method getter; |
| private Class type; |
| private boolean isArray; |
| private boolean isList; |
| |
| /* |
| * dynamic fields may use a Map based data structure to bind a given field. |
| * if a mapping is done using, "Map<String, List<String>> foo", <code>isContainedInMap</code> |
| * is set to <code>TRUE</code> as well as <code>isList</code> is set to <code>TRUE</code> |
| */ |
| private boolean isContainedInMap; |
| private Pattern dynamicFieldNamePatternMatcher; |
| |
| public DocField(AccessibleObject member) { |
| if (member instanceof java.lang.reflect.Field) { |
| field = (java.lang.reflect.Field) member; |
| } else { |
| setter = (Method) member; |
| } |
| Field annotation = member.getAnnotation(Field.class); |
| storeName(annotation); |
| storeType(); |
| |
| // Look for a matching getter |
| if (setter != null) { |
| String gname = setter.getName(); |
| if (gname.startsWith("set")) { |
| gname = "get" + gname.substring(3); |
| try { |
| getter = setter.getDeclaringClass().getMethod(gname, (Class[]) null); |
| } catch (Exception ex) { |
| // no getter -- don't worry about it... |
| if (type == Boolean.class) { |
| gname = "is" + setter.getName().substring(3); |
| try { |
| getter = setter.getDeclaringClass().getMethod(gname, (Class[]) null); |
| } catch(Exception ex2) { |
| // no getter -- don't worry about it... |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void storeName(Field annotation) { |
| if (annotation.value().equals(Field.DEFAULT)) { |
| if (field != null) { |
| name = field.getName(); |
| } else { |
| String setterName = setter.getName(); |
| if (setterName.startsWith("set") && setterName.length() > 3) { |
| name = setterName.substring(3, 4).toLowerCase(Locale.ROOT) + setterName.substring(4); |
| } else { |
| name = setter.getName(); |
| } |
| } |
| } else if (annotation.value().indexOf('*') >= 0) { //dynamic fields are annotated as @Field("categories_*") |
| //if the field was annotated as a dynamic field, convert the name into a pattern |
| //the wildcard (*) is supposed to be either a prefix or a suffix, hence the use of replaceFirst |
| name = annotation.value().replaceFirst("\\*", "\\.*"); |
| dynamicFieldNamePatternMatcher = Pattern.compile("^"+name+"$"); |
| } else { |
| name = annotation.value(); |
| } |
| } |
| |
| private void storeType() { |
| if (field != null) { |
| type = field.getType(); |
| } else { |
| Class[] params = setter.getParameterTypes(); |
| if (params.length != 1) { |
| throw new BindingException("Invalid setter method. Must have one and only one parameter"); |
| } |
| type = params[0]; |
| } |
| |
| if(type == Collection.class || type == List.class || type == ArrayList.class) { |
| type = Object.class; |
| isList = true; |
| } else if (type == byte[].class) { |
| //no op |
| } else if (type.isArray()) { |
| isArray = true; |
| type = type.getComponentType(); |
| } else if (type == Map.class || type == HashMap.class) { //corresponding to the support for dynamicFields |
| isContainedInMap = true; |
| //assigned a default type |
| type = Object.class; |
| if (field != null) { |
| if (field.getGenericType() instanceof ParameterizedType) { |
| //check what are the generic values |
| ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType(); |
| Type[] types = parameterizedType.getActualTypeArguments(); |
| if (types != null && types.length == 2 && types[0] == String.class) { |
| //the key should always be String |
| //Raw and primitive types |
| if (types[1] instanceof Class) { |
| //the value could be multivalued then it is a List, Collection, ArrayList |
| if (types[1]== Collection.class || types[1] == List.class || types[1] == ArrayList.class) { |
| type = Object.class; |
| isList = true; |
| } else { |
| //else assume it is a primitive and put in the source type itself |
| type = (Class) types[1]; |
| } |
| } else if (types[1] instanceof ParameterizedType) { //Of all the Parameterized types, only List is supported |
| Type rawType = ((ParameterizedType)types[1]).getRawType(); |
| if(rawType== Collection.class || rawType == List.class || rawType == ArrayList.class){ |
| type = Object.class; |
| isList = true; |
| } |
| } else if (types[1] instanceof GenericArrayType) { //Array types |
| type = (Class) ((GenericArrayType) types[1]).getGenericComponentType(); |
| isArray = true; |
| } else { //Throw an Exception if types are not known |
| throw new BindingException("Allowed type for values of mapping a dynamicField are : " + |
| "Object, Object[] and List"); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Called by the {@link #inject} method to read the value(s) for a field |
| * This method supports reading of all "matching" fieldName's in the <code>SolrDocument</code> |
| * |
| * Returns <code>SolrDocument.getFieldValue</code> for regular fields, |
| * and <code>Map<String, List<Object>></code> for a dynamic field. The key is all matching fieldName's. |
| */ |
| @SuppressWarnings("unchecked") |
| private Object getFieldValue(SolrDocument solrDocument) { |
| Object fieldValue = solrDocument.getFieldValue(name); |
| if (fieldValue != null) { |
| //this is not a dynamic field. so return the value |
| return fieldValue; |
| } |
| |
| if (dynamicFieldNamePatternMatcher == null) { |
| return null; |
| } |
| |
| //reading dynamic field values |
| Map<String, Object> allValuesMap = null; |
| List allValuesList = null; |
| if (isContainedInMap) { |
| allValuesMap = new HashMap<>(); |
| } else { |
| allValuesList = new ArrayList(); |
| } |
| |
| for (String field : solrDocument.getFieldNames()) { |
| if (dynamicFieldNamePatternMatcher.matcher(field).find()) { |
| Object val = solrDocument.getFieldValue(field); |
| if (val == null) { |
| continue; |
| } |
| |
| if (isContainedInMap) { |
| if (isList) { |
| if (!(val instanceof List)) { |
| List al = new ArrayList(); |
| al.add(val); |
| val = al; |
| } |
| } else if (isArray) { |
| if (!(val instanceof List)) { |
| Object[] arr = (Object[]) Array.newInstance(type, 1); |
| arr[0] = val; |
| val = arr; |
| } else { |
| val = Array.newInstance(type, ((List) val).size()); |
| } |
| } |
| allValuesMap.put(field, val); |
| } else { |
| if (val instanceof Collection) { |
| allValuesList.addAll((Collection) val); |
| } else { |
| allValuesList.add(val); |
| } |
| } |
| } |
| } |
| if (isContainedInMap) { |
| return allValuesMap.isEmpty() ? null : allValuesMap; |
| } else { |
| return allValuesList.isEmpty() ? null : allValuesList; |
| } |
| } |
| |
| <T> void inject(T obj, SolrDocument sdoc) { |
| Object val = getFieldValue(sdoc); |
| if(val == null) { |
| return; |
| } |
| |
| if (isArray && !isContainedInMap) { |
| List list; |
| if (val.getClass().isArray()) { |
| set(obj, val); |
| return; |
| } else if (val instanceof List) { |
| list = (List) val; |
| } else { |
| list = new ArrayList(); |
| list.add(val); |
| } |
| set(obj, list.toArray((Object[]) Array.newInstance(type, list.size()))); |
| } else if (isList && !isContainedInMap) { |
| if (!(val instanceof List)) { |
| List list = new ArrayList(); |
| list.add(val); |
| val = list; |
| } |
| set(obj, val); |
| } else if (isContainedInMap) { |
| if (val instanceof Map) { |
| set(obj, val); |
| } |
| } else { |
| set(obj, val); |
| } |
| |
| } |
| |
| private void set(Object obj, Object v) { |
| if (v != null && type == ByteBuffer.class && v.getClass() == byte[].class) { |
| v = ByteBuffer.wrap((byte[]) v); |
| } |
| try { |
| if (field != null) { |
| field.set(obj, v); |
| } else if (setter != null) { |
| setter.invoke(obj, v); |
| } |
| } |
| catch (Exception e) { |
| throw new BindingException("Exception while setting value : " + v + " on " + (field != null ? field : setter), e); |
| } |
| } |
| |
| public Object get(final Object obj) { |
| if (field != null) { |
| try { |
| return field.get(obj); |
| } catch (Exception e) { |
| throw new BindingException("Exception while getting value: " + field, e); |
| } |
| } else if (getter == null) { |
| throw new BindingException("Missing getter for field: " + name + " -- You can only call the 'get' for fields that have a field of 'get' method"); |
| } |
| |
| try { |
| return getter.invoke(obj, (Object[]) null); |
| } catch (Exception e) { |
| throw new BindingException("Exception while getting value: " + getter, e); |
| } |
| } |
| } |
| } |