/*
* Copyright (C) 2014 Jörg Prante
*
* Licensed 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.xbib.elasticsearch.common.util;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
/**
* A plain indexable object. The indexable object can store meta data and core data.
* The indexable object can be iterated and passed to an XContentBuilder. The individual values
* are formatted for JSON, which should be the correct format
*/
public class PlainIndexableObject implements IndexableObject, ToXContent, Comparable<IndexableObject> {
private final Map<String, String> meta;
private Map<String, Object> core;
private final Params params;
public PlainIndexableObject() {
this(ToXContent.EMPTY_PARAMS);
}
public PlainIndexableObject(Params params) {
this.meta = new LinkedHashMap<String, String>();
this.core = new LinkedHashMap<String, Object>();
this.params = params;
}
@Override
public IndexableObject optype(String optype) {
meta.put(ControlKeys._optype.name(), optype);
return this;
}
@Override
public String optype() {
return meta.get(ControlKeys._optype.name());
}
@Override
public IndexableObject index(String index) {
meta.put(ControlKeys._index.name(), index);
return this;
}
@Override
public String index() {
return meta.get(ControlKeys._index.name());
}
@Override
public IndexableObject type(String type) {
meta.put(ControlKeys._type.name(), type);
return this;
}
@Override
public String type() {
return meta.get(ControlKeys._type.name());
}
@Override
public IndexableObject id(String id) {
meta.put(ControlKeys._id.name(), id);
return this;
}
@Override
public String id() {
return meta.get(ControlKeys._id.name());
}
@Override
public IndexableObject meta(String key, String value) {
meta.put(key, value);
return this;
}
@Override
public String meta(String key) {
return meta.get(key);
}
@Override
public IndexableObject source(Map<String, Object> source) {
this.core = source;
return this;
}
@Override
public Map<String, Object> source() {
return core;
}
/**
* Build a string that can be used for indexing.
*
* @throws java.io.IOException when build gave an error
*/
@Override
public String build() throws IOException {
XContentBuilder builder = jsonBuilder();
toXContent(builder, params);
return builder.string();
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
toXContent(builder, params, core);
return builder;
}
/**
* Recursive method to build XContent from a key/value map of Values
*
* @param builder the builder
* @param params the params
* @param map the map
* @return the XContent builder
* @throws java.io.IOException when method gave an error
*/
@SuppressWarnings({"unchecked"})
protected XContentBuilder toXContent(XContentBuilder builder, Params params, Map<String, Object> map) throws IOException {
builder.startObject();
if (checkCollapsedMapLength(map)) {
builder.endObject();
return builder;
}
for (Map.Entry<String, Object> k : map.entrySet()) {
Object o = k.getValue();
if (params.paramAsBoolean("ignore_null", false) && (o == null || (o instanceof Values) && ((Values) o).isNull())) {
continue;
}
builder.field(k.getKey());
if (o instanceof Values) {
Values v = (Values) o;
v.toXContent(builder, params);
} else if (o instanceof Map) {
toXContent(builder, params, (Map<String, Object>) o);
} else if (o instanceof List) {
toXContent(builder, params, (List) o);
} else {
try {
builder.value(o);
} catch (Throwable e) {
throw new IOException("unknown object class for value:" + o.getClass().getName() + " " + o);
}
}
}
builder.endObject();
return builder;
}
/**
* Check if the map is empty, after optional null value removal.
*
* @param map the map to check
* @return true if map is empty, false if not
*/
protected boolean checkCollapsedMapLength(Map<String, Object> map) {
int exists = 0;
for (Map.Entry<String, Object> k : map.entrySet()) {
Object o = k.getValue();
if (params.paramAsBoolean("ignore_null", false) && (o == null || (o instanceof Values) && ((Values) o).isNull())) {
continue;
}
exists++;
}
return exists == 0;
}
@SuppressWarnings({"unchecked"})
protected XContentBuilder toXContent(XContentBuilder builder, Params params, List list) throws IOException {
builder.startArray();
for (Object o : list) {
if (o instanceof Values) {
Values v = (Values) o;
v.toXContent(builder, ToXContent.EMPTY_PARAMS);
} else if (o instanceof Map) {
if (!checkCollapsedMapLength((Map<String, Object>) o)) {
toXContent(builder, params, (Map<String, Object>) o);
}
} else if (o instanceof List) {
toXContent(builder, params, (List) o);
} else {
try {
builder.value(o);
} catch (Exception e) {
throw new IOException("unknown object class:" + o.getClass().getName());
}
}
}
builder.endArray();
return builder;
}
@Override
public boolean isEmpty() {
return optype() == null && index() == null && type() == null && id() == null && core.isEmpty();
}
@Override
public String toString() {
return "[" + optype() + "/" + index() + "/" + type() + "/" + id() + "]->" + core;
}
@Override
public int compareTo(IndexableObject o) {
if (o == null) {
return -1;
}
String s1 = optype() + '/' + index() + '/' + type() + '/' + id();
String s2 = o.optype() + '/' + o.index() + '/' + o.type() + '/' + o.id();
return s1.compareTo(s2);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof IndexableObject)) {
return false;
}
IndexableObject indexableObject = (IndexableObject)o;
return new EqualsBuilder()
.append(optype(), indexableObject.optype())
.append(index(), indexableObject.index())
.append(type(), indexableObject.type())
.append(id(), indexableObject.id())
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(optype())
.append(index())
.append(type())
.append(id())
.toHashCode();
}
}