mirror of https://github.com/apache/lucene.git
SOLR-536: Add a DocumentObjectBinder to solrj that converts Objects to and from SolrDocuments.
git-svn-id: https://svn.apache.org/repos/asf/lucene/solr/trunk@663643 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
17c90ce718
commit
f2ce567269
|
@ -286,6 +286,9 @@ New Features
|
|||
the global Lucene Similarity implementation.
|
||||
(ehatcher)
|
||||
|
||||
51. SOLR-536: Add a DocumentObjectBinder to solrj that converts Objects to and
|
||||
from SolrDocuments. (Noble Paul via ryan)
|
||||
|
||||
Changes in runtime behavior
|
||||
1. SOLR-559: use Lucene updateDocument, deleteDocuments methods. This
|
||||
removes the maxBufferedDeletes parameter added by SOLR-310 as Lucene
|
||||
|
|
|
@ -0,0 +1,256 @@
|
|||
/**
|
||||
* 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.AccessibleObject;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A class to map objects to and from solr documents.
|
||||
*
|
||||
* @version $Id: ClientUtils.java 601900 2007-12-06 22:55:47Z ryan $
|
||||
* @since solr 1.3
|
||||
*/
|
||||
public class DocumentObjectBinder {
|
||||
private final Map<Class, List<DocField>> infocache =
|
||||
Collections.synchronizedMap( new HashMap<Class, List<DocField>>() );
|
||||
|
||||
public DocumentObjectBinder() {
|
||||
}
|
||||
|
||||
public <T> List<T> getBeans(Class<T> clazz, SolrDocumentList solrDocList) {
|
||||
List<DocField> fields = getDocFields( clazz );
|
||||
List<T> result = new ArrayList<T>(solrDocList.size());
|
||||
|
||||
for(int j=0;j<solrDocList.size();j++) {
|
||||
SolrDocument sdoc = solrDocList.get(j);
|
||||
|
||||
T obj = null;
|
||||
try {
|
||||
obj = clazz.newInstance();
|
||||
result.add(obj);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not instantiate object of " + clazz,e);
|
||||
}
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
DocField docField = fields.get(i);
|
||||
docField.inject(obj, sdoc);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public SolrInputDocument toSolrInputDocument( Object obj )
|
||||
{
|
||||
List<DocField> fields = getDocFields( obj.getClass() );
|
||||
if( fields.isEmpty() ) {
|
||||
throw new RuntimeException( "class: "+obj.getClass()+" does not define any fields." );
|
||||
}
|
||||
|
||||
SolrInputDocument doc = new SolrInputDocument();
|
||||
for( DocField field : fields ) {
|
||||
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<DocField>();
|
||||
Class superClazz = clazz;
|
||||
ArrayList<AccessibleObject> members = new ArrayList<AccessibleObject>();
|
||||
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 = false, isList=false;
|
||||
|
||||
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() + setterName.substring(4);
|
||||
} else {
|
||||
name = setter.getName();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
name = annotation.value();
|
||||
}
|
||||
}
|
||||
|
||||
private void storeType() {
|
||||
if (field != null) {
|
||||
type = field.getType();
|
||||
} else {
|
||||
Class[] params = setter.getParameterTypes();
|
||||
if (params.length != 1)
|
||||
throw new RuntimeException("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;
|
||||
/*ParameterizedType parameterizedType = null;
|
||||
if(field !=null){
|
||||
if( field.getGenericType() instanceof ParameterizedType){
|
||||
parameterizedType = (ParameterizedType) field.getGenericType();
|
||||
Type[] types = parameterizedType.getActualTypeArguments();
|
||||
if (types != null && types.length > 0) type = (Class) types[0];
|
||||
}
|
||||
}*/
|
||||
} else if (type.isArray()) {
|
||||
isArray = true;
|
||||
type = type.getComponentType();
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void inject(T obj, SolrDocument sdoc) {
|
||||
Object val = sdoc.getFieldValue(name);
|
||||
if(val == null) return;
|
||||
if (isArray) {
|
||||
if (val instanceof List) {
|
||||
List collection = (List) val;
|
||||
set(obj, collection.toArray((Object[]) Array.newInstance(type,collection.size())));
|
||||
} else {
|
||||
set(obj, new Object[]{val});
|
||||
}
|
||||
} else if (isList) {
|
||||
if (val instanceof List) {
|
||||
set(obj, val);
|
||||
} else {
|
||||
ArrayList l = new ArrayList();
|
||||
l.add(val);
|
||||
set(obj, l);
|
||||
}
|
||||
} else {
|
||||
if (val instanceof List) {
|
||||
List l = (List) val;
|
||||
if(l.size()>0)
|
||||
set(obj, l.get(0));
|
||||
}
|
||||
else {
|
||||
set(obj,val) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void set(Object obj, Object v) {
|
||||
try {
|
||||
if (field != null) {
|
||||
field.set(obj, v);
|
||||
} else if (setter != null) {
|
||||
setter.invoke(obj, v);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("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 RuntimeException("Exception while getting value: " + field, e);
|
||||
}
|
||||
}
|
||||
else if (getter == null) {
|
||||
throw new RuntimeException( "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 RuntimeException("Exception while getting value: " + getter, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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 static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
|
||||
/**
|
||||
* @version $Id: ClientUtils.java 601900 2007-12-06 22:55:47Z ryan $
|
||||
* @since solr 1.3
|
||||
*/
|
||||
@Target({FIELD, METHOD, TYPE})
|
||||
@Retention(RUNTIME)
|
||||
public @interface Field {
|
||||
public static final String DEFAULT ="#default";
|
||||
String value() default DEFAULT;
|
||||
}
|
|
@ -82,6 +82,19 @@ public class ClientUtils
|
|||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SolrInputDocument to convert
|
||||
* @return a SolrDocument with the same fields and values as the SolrInputDocument
|
||||
*/
|
||||
public static SolrDocument toSolrDocument( SolrInputDocument d )
|
||||
{
|
||||
SolrDocument doc = new SolrDocument();
|
||||
for( SolrInputField field : d ) {
|
||||
doc.setField( field.getName(), field.getValue() );
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* 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 junit.framework.TestCase;
|
||||
|
||||
import org.apache.solr.client.solrj.beans.DocumentObjectBinder;
|
||||
import org.apache.solr.client.solrj.beans.Field;
|
||||
import org.apache.solr.client.solrj.impl.XMLResponseParser;
|
||||
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||
import org.apache.solr.client.solrj.util.ClientUtils;
|
||||
import org.apache.solr.common.SolrDocumentList;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.apache.solr.common.SolrInputField;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class TestDocumentObjectBinder extends TestCase
|
||||
{
|
||||
public void testSimple() throws Exception {
|
||||
DocumentObjectBinder binder = new DocumentObjectBinder();
|
||||
XMLResponseParser parser = new XMLResponseParser();
|
||||
NamedList<Object> nl = null;
|
||||
nl = parser.processResponse(new StringReader(xml));
|
||||
QueryResponse res = new QueryResponse(nl);
|
||||
SolrDocumentList solDocList = res.getResults();
|
||||
List<Item> l = binder.getBeans(Item.class,res.getResults());
|
||||
Assert.assertEquals(solDocList.size(), l.size());
|
||||
Assert.assertEquals(solDocList.get(0).getFieldValue("features"), l.get(0).features);
|
||||
|
||||
Item item = new Item();
|
||||
item.id = "aaa";
|
||||
item.categories = new String[] { "aaa", "bbb", "ccc" };
|
||||
SolrInputDocument out = binder.toSolrInputDocument( item );
|
||||
|
||||
Assert.assertEquals( item.id, out.getFieldValue( "id" ) );
|
||||
SolrInputField catfield = out.getField( "cat" );
|
||||
Assert.assertEquals( 3, catfield.getValueCount() );
|
||||
Assert.assertEquals( "[aaa, bbb, ccc]", catfield.getValue().toString() );
|
||||
|
||||
// Test the error on not settable stuff...
|
||||
NotGettableItem ng = new NotGettableItem();
|
||||
ng.setInStock( false );
|
||||
try {
|
||||
out = binder.toSolrInputDocument( ng );
|
||||
Assert.fail( "Should throw an error" );
|
||||
}
|
||||
catch( RuntimeException ex ) {
|
||||
// ok -- this should happen...
|
||||
}
|
||||
}
|
||||
|
||||
public void testToAndFromSolrDocument()
|
||||
{
|
||||
Item item = new Item();
|
||||
item.id = "one";
|
||||
item.inStock = false;
|
||||
item.categories = new String[] { "aaa", "bbb", "ccc" };
|
||||
item.features = Arrays.asList( item.categories );
|
||||
|
||||
DocumentObjectBinder binder = new DocumentObjectBinder();
|
||||
SolrInputDocument doc = binder.toSolrInputDocument( item );
|
||||
SolrDocumentList docs = new SolrDocumentList();
|
||||
docs.add( ClientUtils.toSolrDocument(doc) );
|
||||
Item out = binder.getBeans( Item.class, docs ).get( 0 );
|
||||
|
||||
// make sure it came out the same
|
||||
Assert.assertEquals( item.id, out.id );
|
||||
Assert.assertEquals( item.inStock, out.inStock );
|
||||
Assert.assertEquals( item.categories.length, out.categories.length );
|
||||
Assert.assertEquals( item.features, out.features );
|
||||
}
|
||||
|
||||
public static class Item {
|
||||
@Field
|
||||
String id;
|
||||
|
||||
@Field("cat")
|
||||
String[] categories;
|
||||
|
||||
@Field
|
||||
List<String> features;
|
||||
|
||||
@Field
|
||||
Date timestamp;
|
||||
|
||||
@Field("highway_mileage")
|
||||
int mwyMileage;
|
||||
|
||||
boolean inStock = false;
|
||||
|
||||
@Field
|
||||
public void setInStock(Boolean b) {
|
||||
inStock = b;
|
||||
}
|
||||
|
||||
// required if you want to fill SolrDocuments with the same annotaion...
|
||||
public boolean isInStock()
|
||||
{
|
||||
return inStock;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class NotGettableItem {
|
||||
@Field
|
||||
String id;
|
||||
|
||||
private boolean inStock;
|
||||
private String aaa;
|
||||
|
||||
@Field
|
||||
public void setInStock(Boolean b) {
|
||||
inStock = b;
|
||||
}
|
||||
|
||||
public String getAaa() {
|
||||
return aaa;
|
||||
}
|
||||
|
||||
@Field
|
||||
public void setAaa(String aaa) {
|
||||
this.aaa = aaa;
|
||||
}
|
||||
}
|
||||
|
||||
public static final String xml =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
||||
"<response>" +
|
||||
"<lst name=\"responseHeader\"><int name=\"status\">0</int><int name=\"QTime\">0</int><lst name=\"params\"><str name=\"start\">0</str><str name=\"q\">*:*\n" +
|
||||
"</str><str name=\"version\">2.2</str><str name=\"rows\">4</str></lst></lst><result name=\"response\" numFound=\"26\" start=\"0\"><doc><arr name=\"cat\">" +
|
||||
"<str>electronics</str><str>hard drive</str></arr><arr name=\"features\"><str>7200RPM, 8MB cache, IDE Ultra ATA-133</str>" +
|
||||
"<str>NoiseGuard, SilentSeek technology, Fluid Dynamic Bearing (FDB) motor</str></arr><str name=\"id\">SP2514N</str>" +
|
||||
"<bool name=\"inStock\">true</bool><str name=\"manu\">Samsung Electronics Co. Ltd.</str><str name=\"name\">Samsung SpinPoint P120 SP2514N - hard drive - 250 GB - ATA-133</str>" +
|
||||
"<int name=\"popularity\">6</int><float name=\"price\">92.0</float><str name=\"sku\">SP2514N</str><date name=\"timestamp\">2008-04-16T10:35:57.078Z</date></doc>" +
|
||||
"<doc><arr name=\"cat\"><str>electronics</str><str>hard drive</str></arr><arr name=\"features\"><str>SATA 3.0Gb/s, NCQ</str><str>8.5ms seek</str>" +
|
||||
"<str>16MB cache</str></arr><str name=\"id\">6H500F0</str><bool name=\"inStock\">true</bool><str name=\"manu\">Maxtor Corp.</str>" +
|
||||
"<str name=\"name\">Maxtor DiamondMax 11 - hard drive - 500 GB - SATA-300</str><int name=\"popularity\">6</int><float name=\"price\">350.0</float>" +
|
||||
"<str name=\"sku\">6H500F0</str><date name=\"timestamp\">2008-04-16T10:35:57.109Z</date></doc><doc><arr name=\"cat\"><str>electronics</str>" +
|
||||
"<str>connector</str></arr><arr name=\"features\"><str>car power adapter, white</str></arr><str name=\"id\">F8V7067-APL-KIT</str>" +
|
||||
"<bool name=\"inStock\">false</bool><str name=\"manu\">Belkin</str><str name=\"name\">Belkin Mobile Power Cord for iPod w/ Dock</str>" +
|
||||
"<int name=\"popularity\">1</int><float name=\"price\">19.95</float><str name=\"sku\">F8V7067-APL-KIT</str>" +
|
||||
"<date name=\"timestamp\">2008-04-16T10:35:57.140Z</date><float name=\"weight\">4.0</float></doc><doc>" +
|
||||
"<arr name=\"cat\"><str>electronics</str><str>connector</str></arr><arr name=\"features\">" +
|
||||
"<str>car power adapter for iPod, white</str></arr><str name=\"id\">IW-02</str><bool name=\"inStock\">false</bool>" +
|
||||
"<str name=\"manu\">Belkin</str><str name=\"name\">iPod & iPod Mini USB 2.0 Cable</str>" +
|
||||
"<int name=\"popularity\">1</int><float name=\"price\">11.5</float><str name=\"sku\">IW-02</str>" +
|
||||
"<date name=\"timestamp\">2008-04-16T10:35:57.140Z</date><float name=\"weight\">2.0</float></doc></result>\n" +
|
||||
"</response>";
|
||||
}
|
Loading…
Reference in New Issue