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
9
* http://www.apache.org/licenses/LICENSE-2.0
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.
17
package org.apache.solr.client.solrj.beans;
19
import org.apache.solr.common.SolrDocumentList;
20
import org.apache.solr.common.SolrDocument;
21
import org.apache.solr.common.SolrInputDocument;
23
import java.lang.reflect.*;
25
import java.util.regex.Pattern;
26
import java.util.concurrent.ConcurrentHashMap;
27
import java.nio.ByteBuffer;
30
* A class to map objects to and from solr documents.
32
* @version $Id: DocumentObjectBinder.java 945270 2010-05-17 17:45:18Z rmuir $
35
public class DocumentObjectBinder {
36
private final Map<Class, List<DocField>> infocache = new ConcurrentHashMap<Class, List<DocField>>();
38
public DocumentObjectBinder() {
41
public <T> List<T> getBeans(Class<T> clazz, SolrDocumentList solrDocList) {
42
List<DocField> fields = getDocFields( clazz );
43
List<T> result = new ArrayList<T>(solrDocList.size());
45
for(int j=0;j<solrDocList.size();j++) {
46
SolrDocument sdoc = solrDocList.get(j);
47
result.add(getBean(clazz, fields, sdoc));
51
public <T> T getBean(Class<T> clazz, SolrDocument solrDoc) {
52
return getBean(clazz, null,solrDoc);
55
private <T> T getBean(Class<T> clazz, List<DocField> fields, SolrDocument solrDoc) {
57
fields = getDocFields(clazz);
61
obj = clazz.newInstance();
62
} catch (Exception e) {
63
throw new RuntimeException("Could not instantiate object of " + clazz, e);
65
for (int i = 0; i < fields.size(); i++) {
66
DocField docField = fields.get(i);
67
docField.inject(obj, solrDoc);
72
public SolrInputDocument toSolrInputDocument( Object obj )
74
List<DocField> fields = getDocFields( obj.getClass() );
75
if( fields.isEmpty() ) {
76
throw new RuntimeException( "class: "+obj.getClass()+" does not define any fields." );
79
SolrInputDocument doc = new SolrInputDocument();
80
for (DocField field : fields) {
81
if (field.dynamicFieldNamePatternMatcher != null
82
&& field.get(obj) != null && field.isContainedInMap) {
83
Map<String, Object> mapValue = (HashMap<String, Object>) field
86
for (Map.Entry<String, Object> e : mapValue.entrySet()) {
87
doc.setField( e.getKey(), e.getValue(), 1.0f);
90
doc.setField(field.name, field.get(obj), 1.0f);
96
private List<DocField> getDocFields( Class clazz )
98
List<DocField> fields = infocache.get(clazz);
100
synchronized(infocache) {
101
infocache.put(clazz, fields = collectInfo(clazz));
107
private List<DocField> collectInfo(Class clazz) {
108
List<DocField> fields = new ArrayList<DocField>();
109
Class superClazz = clazz;
110
ArrayList<AccessibleObject> members = new ArrayList<AccessibleObject>();
111
while (superClazz != null && superClazz != Object.class) {
112
members.addAll(Arrays.asList(superClazz.getDeclaredFields()));
113
members.addAll(Arrays.asList(superClazz.getDeclaredMethods()));
114
superClazz = superClazz.getSuperclass();
116
for (AccessibleObject member : members) {
117
if (member.isAnnotationPresent(Field.class)) {
118
member.setAccessible(true);
119
fields.add(new DocField(member));
125
private static class DocField {
127
private java.lang.reflect.Field field;
128
private Method setter;
129
private Method getter;
131
private boolean isArray = false, isList=false;
134
* dynamic fields may use a Map based data structure to bind a given field.
135
* if a mapping is done using, "Map<String, List<String>> foo", <code>isContainedInMap</code>
136
* is set to <code>TRUE</code> as well as <code>isList</code> is set to <code>TRUE</code>
138
boolean isContainedInMap =false;
139
private Pattern dynamicFieldNamePatternMatcher;
141
public DocField(AccessibleObject member) {
142
if (member instanceof java.lang.reflect.Field) {
143
field = (java.lang.reflect.Field) member;
145
setter = (Method) member;
147
Field annotation = member.getAnnotation(Field.class);
148
storeName(annotation);
151
// Look for a matching getter
152
if( setter != null ) {
153
String gname = setter.getName();
154
if( gname.startsWith("set") ) {
155
gname = "get" + gname.substring(3);
157
getter = setter.getDeclaringClass().getMethod( gname, (Class[])null );
159
catch( Exception ex ) {
160
// no getter -- don't worry about it...
161
if( type == Boolean.class ) {
162
gname = "is" + setter.getName().substring( 3 );
164
getter = setter.getDeclaringClass().getMethod( gname, (Class[])null );
166
catch( Exception ex2 ) {
167
// no getter -- don't worry about it...
175
private void storeName(Field annotation) {
176
if (annotation.value().equals(Field.DEFAULT)) {
178
name = field.getName();
180
String setterName = setter.getName();
181
if (setterName.startsWith("set") && setterName.length() > 3) {
182
name = setterName.substring(3, 4).toLowerCase(Locale.ENGLISH) + setterName.substring(4);
184
name = setter.getName();
188
//dynamic fields are annotated as @Field("categories_*")
189
else if(annotation.value().indexOf('*') >= 0){
190
//if the field was annotated as a dynamic field, convert the name into a pattern
191
//the wildcard (*) is supposed to be either a prefix or a suffix, hence the use of replaceFirst
192
name = annotation.value().replaceFirst("\\*", "\\.*");
193
dynamicFieldNamePatternMatcher = Pattern.compile("^"+name+"$");
195
name = annotation.value();
199
private void storeType() {
201
type = field.getType();
203
Class[] params = setter.getParameterTypes();
204
if (params.length != 1)
205
throw new RuntimeException("Invalid setter method. Must have one and only one parameter");
208
if(type == Collection.class || type == List.class || type == ArrayList.class) {
211
/*ParameterizedType parameterizedType = null;
213
if( field.getGenericType() instanceof ParameterizedType){
214
parameterizedType = (ParameterizedType) field.getGenericType();
215
Type[] types = parameterizedType.getActualTypeArguments();
216
if (types != null && types.length > 0) type = (Class) types[0];
219
} else if(type == byte[].class){
221
}else if (type.isArray()) {
223
type = type.getComponentType();
225
//corresponding to the support for dynamicFields
226
else if (type == Map.class || type == HashMap.class) {
227
isContainedInMap = true;
228
//assigned a default type
231
if(field.getGenericType() instanceof ParameterizedType){
232
//check what are the generic values
233
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
234
Type[] types = parameterizedType.getActualTypeArguments();
235
if(types != null && types.length == 2 && types[0] == String.class){
236
//the key should always be String
237
//Raw and primitive types
238
if(types[1] instanceof Class){
239
//the value could be multivalued then it is a List ,Collection,ArrayList
240
if(types[1]== Collection.class || types[1] == List.class || types[1] == ArrayList.class){
244
//else assume it is a primitive and put in the source type itself
245
type = (Class) types[1];
248
//Of all the Parameterized types, only List is supported
249
else if(types[1] instanceof ParameterizedType){
250
Type rawType = ((ParameterizedType)types[1]).getRawType();
251
if(rawType== Collection.class || rawType == List.class || rawType == ArrayList.class){
257
else if(types[1] instanceof GenericArrayType){
258
type = (Class) ((GenericArrayType) types[1]).getGenericComponentType();
261
//Throw an Exception if types are not known
263
throw new RuntimeException("Allowed type for values of mapping a dynamicField are : " +
264
"Object, Object[] and List");
273
* Called by the {@link #inject} method to read the value(s) for a field
274
* This method supports reading of all "matching" fieldName's in the <code>SolrDocument</code>
276
* Returns <code>SolrDocument.getFieldValue</code> for regular fields,
277
* and <code>Map<String, List<Object>></code> for a dynamic field. The key is all matching fieldName's.
279
@SuppressWarnings("unchecked")
280
private Object getFieldValue(SolrDocument sdoc){
281
Object fieldValue = sdoc.getFieldValue(name);
282
if(fieldValue != null) {
283
//this is not a dynamic field. so return te value
286
//reading dynamic field values
287
if(dynamicFieldNamePatternMatcher != null){
288
Map<String, Object> allValuesMap = null;
289
ArrayList allValuesList = null;
290
if(isContainedInMap){
291
allValuesMap = new HashMap<String, Object>();
293
allValuesList = new ArrayList();
295
for(String field : sdoc.getFieldNames()){
296
if(dynamicFieldNamePatternMatcher.matcher(field).find()){
297
Object val = sdoc.getFieldValue(field);
298
if(val == null) continue;
299
if(isContainedInMap){
301
if (!(val instanceof List)) {
302
ArrayList al = new ArrayList();
307
if (!(val instanceof List)) {
308
Object[] arr= (Object[]) Array.newInstance(type,1);
312
val = Array.newInstance(type,((List)val).size());
315
allValuesMap.put(field, val);
317
if (val instanceof Collection) {
318
allValuesList.addAll((Collection) val);
320
allValuesList.add(val);
325
if (isContainedInMap) {
326
return allValuesMap.isEmpty() ? null : allValuesMap;
328
return allValuesList.isEmpty() ? null : allValuesList;
333
<T> void inject(T obj, SolrDocument sdoc) {
334
Object val = getFieldValue(sdoc);
338
if(isArray && !isContainedInMap){
340
if(val.getClass().isArray()){
343
} else if (val instanceof List) {
346
list = new ArrayList();
349
set(obj, list.toArray((Object[]) Array.newInstance(type,list.size())));
350
} else if(isList && !isContainedInMap){
351
if (!(val instanceof List)) {
352
ArrayList list = new ArrayList();
357
} else if(isContainedInMap){
358
if (val instanceof Map) {
368
private void set(Object obj, Object v) {
369
if(v!= null && type == ByteBuffer.class && v.getClass()== byte[].class) {
370
v = ByteBuffer.wrap((byte[])v);
375
} else if (setter != null) {
376
setter.invoke(obj, v);
379
catch (Exception e) {
380
throw new RuntimeException("Exception while setting value : "+v+" on " + (field != null ? field : setter), e);
384
public Object get( final Object obj )
388
return field.get(obj);
390
catch (Exception e) {
391
throw new RuntimeException("Exception while getting value: " + field, e);
394
else if (getter == null) {
395
throw new RuntimeException( "Missing getter for field: "+name+" -- You can only call the 'get' for fields that have a field of 'get' method" );
399
return getter.invoke( obj, (Object[])null );
401
catch (Exception e) {
402
throw new RuntimeException("Exception while getting value: " + getter, e);