| /* |
| * 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.common; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.BiConsumer; |
| |
| import org.apache.solr.common.util.NamedList; |
| |
| |
| /** |
| * A concrete representation of a document within a Solr index. Unlike a lucene |
| * Document, a SolrDocument may have an Object value matching the type defined in |
| * schema.xml |
| * |
| * For indexing documents, use the SolrInputDocument that contains extra information |
| * for document and field boosting. |
| * |
| * |
| * @since solr 1.3 |
| */ |
| public class SolrDocument extends SolrDocumentBase<Object, SolrDocument> implements Iterable<Map.Entry<String, Object>> |
| { |
| protected final Map<String,Object> _fields; |
| |
| private List<SolrDocument> _childDocuments; |
| |
| public SolrDocument() |
| { |
| _fields = new LinkedHashMap<>(); |
| } |
| |
| @Override |
| public void writeMap(EntryWriter ew) throws IOException { |
| _fields.forEach(ew.getBiConsumer()); |
| } |
| |
| public SolrDocument(Map<String, Object> fields) { |
| this._fields = fields; |
| } |
| |
| /** |
| * @return a list of field names defined in this document - this Collection is directly backed by this SolrDocument. |
| * @see #keySet |
| */ |
| @Override |
| public Collection<String> getFieldNames() { |
| return this.keySet(); |
| } |
| |
| /////////////////////////////////////////////////////////////////// |
| // Add / Set / Remove Fields |
| /////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Remove all fields from the document |
| */ |
| @Override |
| public void clear() |
| { |
| _fields.clear(); |
| |
| if(_childDocuments != null) { |
| _childDocuments.clear(); |
| } |
| } |
| |
| /** |
| * Remove all fields with the name |
| */ |
| public boolean removeFields(String name) |
| { |
| return this.remove( name ) != null; |
| } |
| |
| /** |
| * Set a field with the given object. If the object is an Array, it will |
| * set multiple fields with the included contents. This will replace any existing |
| * field with the given name |
| */ |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public void setField(String name, Object value) |
| { |
| if( value instanceof Object[] ) { |
| value = new ArrayList(Arrays.asList( (Object[])value )); |
| } |
| else if( value instanceof Collection ) { |
| // nothing |
| } |
| else if( value instanceof NamedList ) { |
| // nothing |
| } |
| else if( value instanceof Iterable && !(value instanceof SolrDocumentBase)) { |
| ArrayList<Object> lst = new ArrayList<>(); |
| for( Object o : (Iterable)value ) { |
| lst.add( o ); |
| } |
| value = lst; |
| } |
| _fields.put(name, value); |
| } |
| |
| /** |
| * This will add a field to the document. If fields already exist with this |
| * name it will append value to the collection. If the value is Collection, |
| * each value will be added independently. |
| * |
| * The class type of value and the name parameter should match schema.xml. |
| * schema.xml can be found in conf directory under the solr home by default. |
| * |
| * @param name Name of the field, should match one of the field names defined under "fields" tag in schema.xml. |
| * @param value Value of the field, should be of same class type as defined by "type" attribute of the corresponding field in schema.xml. |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public void addField(String name, Object value) |
| { |
| Object existing = _fields.get(name); |
| if (existing == null) { |
| if( value instanceof Collection ) { |
| Collection<Object> c = new ArrayList<>( 3 ); |
| for ( Object o : (Collection<Object>)value ) { |
| c.add(o); |
| } |
| this.setField( name, c ); |
| } else { |
| this.setField( name, value ); |
| } |
| return; |
| } |
| |
| Collection<Object> vals = null; |
| if( existing instanceof Collection ) { |
| vals = (Collection<Object>)existing; |
| } |
| else { |
| vals = new ArrayList<>( 3 ); |
| vals.add( existing ); |
| } |
| |
| // Add the values to the collection |
| if( value instanceof Iterable && !(value instanceof SolrDocumentBase)) { |
| for( Object o : (Iterable<Object>)value ) { |
| vals.add( o ); |
| } |
| } |
| else if( value instanceof Object[] ) { |
| for( Object o : (Object[])value ) { |
| vals.add( o ); |
| } |
| } |
| else { |
| vals.add( value ); |
| } |
| _fields.put( name, vals ); |
| } |
| |
| /////////////////////////////////////////////////////////////////// |
| // Get the field values |
| /////////////////////////////////////////////////////////////////// |
| |
| /** |
| * returns the first value for a field |
| */ |
| public Object getFirstValue(String name) { |
| Object v = _fields.get( name ); |
| if (v == null || !(v instanceof Collection)) return v; |
| @SuppressWarnings({"rawtypes"}) |
| Collection c = (Collection)v; |
| if (c.size() > 0 ) { |
| return c.iterator().next(); |
| } |
| return null; |
| } |
| |
| /** |
| * Get the value or collection of values for a given field. |
| */ |
| @Override |
| public Object getFieldValue(String name) { |
| return _fields.get( name ); |
| } |
| |
| /** |
| * Get a collection of values for a given field name |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public Collection<Object> getFieldValues(String name) { |
| Object v = _fields.get( name ); |
| if( v instanceof Collection ) { |
| return (Collection<Object>)v; |
| } |
| if( v != null ) { |
| ArrayList<Object> arr = new ArrayList<>(1); |
| arr.add( v ); |
| return arr; |
| } |
| return null; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "SolrDocument"+_fields; |
| } |
| |
| /** |
| * Iterate of String->Object keys |
| */ |
| @Override |
| public Iterator<Entry<String, Object>> iterator() { |
| return _fields.entrySet().iterator(); |
| } |
| |
| /** Beta API; may change at will. */ |
| // TODO SOLR-15063 reconcile SolrDocumentBase/SolrDocument/SolrInputDocument debacle |
| public void visitSelfAndNestedDocs(BiConsumer<String, SolrDocument> consumer) { |
| consumer.accept(null, this); |
| for (Entry<String, Object> keyVal : entrySet()) { |
| final Object value = keyVal.getValue(); |
| if (value instanceof SolrDocument) { |
| consumer.accept(keyVal.getKey(), (SolrDocument) value); |
| } else if (value instanceof Collection) { |
| Collection<?> cVal = (Collection<?>) value; |
| for (Object v : cVal) { |
| if (v instanceof SolrDocument) { |
| consumer.accept(keyVal.getKey(), (SolrDocument) v); |
| } else { |
| break; // either they are all SolrDocs, or none are |
| } |
| } |
| } |
| } |
| |
| if (_childDocuments != null) { |
| for (SolrDocument childDocument : _childDocuments) { |
| consumer.accept(null, childDocument); |
| } |
| } |
| } |
| |
| //----------------------------------------------------------------------------------------- |
| // JSTL Helpers |
| //----------------------------------------------------------------------------------------- |
| |
| /** |
| * Expose a Map interface to the solr field value collection. |
| */ |
| public Map<String,Collection<Object>> getFieldValuesMap() |
| { |
| return new Map<String,Collection<Object>>() { |
| /** Get the field Value */ |
| @Override |
| public Collection<Object> get(Object key) { |
| return getFieldValues( (String)key ); |
| } |
| |
| // Easily Supported methods |
| @Override |
| public boolean containsKey(Object key) { return _fields.containsKey( key ); } |
| @Override |
| public Set<String> keySet() { return _fields.keySet(); } |
| @Override |
| public int size() { return _fields.size(); } |
| @Override |
| public boolean isEmpty() { return _fields.isEmpty(); } |
| |
| // Unsupported operations. These are not necessary for JSTL |
| @Override |
| public void clear() { throw new UnsupportedOperationException(); } |
| @Override |
| public boolean containsValue(Object value) {throw new UnsupportedOperationException();} |
| @Override |
| public Set<java.util.Map.Entry<String, Collection<Object>>> entrySet() {throw new UnsupportedOperationException();} |
| @Override |
| public void putAll(Map<? extends String, ? extends Collection<Object>> t) {throw new UnsupportedOperationException();} |
| @Override |
| public Collection<Collection<Object>> values() {throw new UnsupportedOperationException();} |
| @Override |
| public Collection<Object> put(String key, Collection<Object> value) {throw new UnsupportedOperationException();} |
| @Override |
| public Collection<Object> remove(Object key) {throw new UnsupportedOperationException();} |
| @Override |
| public String toString() {return _fields.toString();} |
| }; |
| } |
| |
| /** |
| * Expose a Map interface to the solr fields. This function is useful for JSTL |
| */ |
| public Map<String,Object> getFieldValueMap() { |
| return new Map<String,Object>() { |
| /** Get the field Value */ |
| @Override |
| public Object get(Object key) { |
| return getFirstValue( (String)key); |
| } |
| |
| // Easily Supported methods |
| @Override |
| public boolean containsKey(Object key) { return _fields.containsKey( key ); } |
| @Override |
| public Set<String> keySet() { return _fields.keySet(); } |
| @Override |
| public int size() { return _fields.size(); } |
| @Override |
| public boolean isEmpty() { return _fields.isEmpty(); } |
| |
| // Unsupported operations. These are not necessary for JSTL |
| @Override |
| public void clear() { throw new UnsupportedOperationException(); } |
| @Override |
| public boolean containsValue(Object value) {throw new UnsupportedOperationException();} |
| @Override |
| public Set<java.util.Map.Entry<String, Object>> entrySet() {throw new UnsupportedOperationException();} |
| @Override |
| public void putAll(Map<? extends String, ? extends Object> t) {throw new UnsupportedOperationException();} |
| @Override |
| public Collection<Object> values() {throw new UnsupportedOperationException();} |
| @Override |
| public Collection<Object> put(String key, Object value) {throw new UnsupportedOperationException();} |
| @Override |
| public Collection<Object> remove(Object key) {throw new UnsupportedOperationException();} |
| @Override |
| public String toString() {return _fields.toString();} |
| }; |
| } |
| |
| //--------------------------------------------------- |
| // MAP interface |
| //--------------------------------------------------- |
| |
| @Override |
| public boolean containsKey(Object key) { |
| return _fields.containsKey(key); |
| } |
| |
| @Override |
| public boolean containsValue(Object value) { |
| return _fields.containsValue(value); |
| } |
| |
| @Override |
| public Set<Entry<String, Object>> entrySet() { |
| return _fields.entrySet(); |
| } |
| //TODO: Shouldn't the input parameter here be a String? The _fields map requires a String. |
| @Override |
| public Object get(Object key) { |
| return _fields.get(key); |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return _fields.isEmpty(); |
| } |
| |
| @Override |
| public Set<String> keySet() { |
| return _fields.keySet(); |
| } |
| |
| @Override |
| public Object put(String key, Object value) { |
| return _fields.put(key, value); |
| } |
| |
| @Override |
| public void putAll(Map<? extends String, ? extends Object> t) { |
| _fields.putAll( t ); |
| } |
| |
| @Override |
| public Object remove(Object key) { |
| return _fields.remove(key); |
| } |
| |
| @Override |
| public int size() { |
| return _fields.size(); |
| } |
| |
| @Override |
| public Collection<Object> values() { |
| return _fields.values(); |
| } |
| |
| @Override |
| public void addChildDocument(SolrDocument child) { |
| if (_childDocuments == null) { |
| _childDocuments = new ArrayList<>(); |
| } |
| _childDocuments.add(child); |
| } |
| |
| @Override |
| public void addChildDocuments(Collection<SolrDocument> children) { |
| for (SolrDocument child : children) { |
| addChildDocument(child); |
| } |
| } |
| |
| @Override |
| public List<SolrDocument> getChildDocuments() { |
| return _childDocuments; |
| } |
| |
| @Override |
| public boolean hasChildDocuments() { |
| boolean isEmpty = (_childDocuments == null || _childDocuments.isEmpty()); |
| return !isEmpty; |
| } |
| |
| @Override |
| |
| @Deprecated |
| public int getChildDocumentCount() { |
| if (_childDocuments == null) return 0; |
| return _childDocuments.size(); |
| } |
| } |