001/*
002 * Copyright (C) 2014 Jörg Prante
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.xbib.elasticsearch.plugin.jdbc.util;
017
018import org.elasticsearch.common.xcontent.ToXContent;
019import org.elasticsearch.common.xcontent.XContentBuilder;
020
021import java.io.IOException;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Map;
025
026import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
027
028/**
029 * A plain indexable object. The indexable object can store meta data and core data.
030 * The indexable object can be iterated and passed to an XContentBuilder. The individual values
031 * are formatted for JSON, which should be the correct format
032 */
033public class PlainIndexableObject implements IndexableObject, ToXContent, Comparable<IndexableObject> {
034
035    private Map<String, String> meta;
036
037    private Map<String, Object> core;
038
039    private boolean ignoreNull;
040
041    public PlainIndexableObject() {
042        this.meta = new LinkedHashMap<String, String>();
043        this.core = new LinkedHashMap<String, Object>();
044    }
045
046    @Override
047    public IndexableObject ignoreNull(boolean ignorenull) {
048        this.ignoreNull = ignorenull;
049        return this;
050    }
051
052    @Override
053    public IndexableObject optype(String optype) {
054        meta.put(ControlKeys._optype.name(), optype);
055        return this;
056    }
057
058    @Override
059    public String optype() {
060        return meta.get(ControlKeys._optype.name());
061    }
062
063    @Override
064    public IndexableObject index(String index) {
065        meta.put(ControlKeys._index.name(), index);
066        return this;
067    }
068
069    @Override
070    public String index() {
071        return meta.get(ControlKeys._index.name());
072    }
073
074    @Override
075    public IndexableObject type(String type) {
076        meta.put(ControlKeys._type.name(), type);
077        return this;
078    }
079
080    @Override
081    public String type() {
082        return meta.get(ControlKeys._type.name());
083    }
084
085    @Override
086    public IndexableObject id(String id) {
087        meta.put(ControlKeys._id.name(), id);
088        return this;
089    }
090
091    @Override
092    public String id() {
093        return meta.get(ControlKeys._id.name());
094    }
095
096    @Override
097    public IndexableObject meta(String key, String value) {
098        meta.put(key, value);
099        return this;
100    }
101
102    @Override
103    public String meta(String key) {
104        return meta.get(key);
105    }
106
107    @Override
108    public IndexableObject source(Map<String, Object> source) {
109        this.core = source;
110        return this;
111    }
112
113    @Override
114    public Map<String, Object> source() {
115        return core;
116    }
117
118    /**
119     * Build a string that can be used for indexing.
120     *
121     * @throws java.io.IOException when build gave an error
122     */
123    @Override
124    public String build() throws IOException {
125        XContentBuilder builder = jsonBuilder();
126        toXContent(builder, ToXContent.EMPTY_PARAMS);
127        return builder.string();
128    }
129
130    @Override
131    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
132        toXContent(builder, params, core);
133        return builder;
134    }
135
136    /**
137     * Recursive method to build XContent from a key/value map of Values
138     *
139     * @param builder the builder
140     * @param map     the map
141     * @return the XContent builder
142     * @throws java.io.IOException when method gave an error
143     */
144    @SuppressWarnings({"unchecked"})
145    protected XContentBuilder toXContent(XContentBuilder builder, Params params, Map<String, Object> map) throws IOException {
146        if (checkCollapsedMapLength(map)) {
147            return builder;
148        }
149        builder.startObject();
150        for (Map.Entry<String, Object> k : map.entrySet()) {
151            Object o = k.getValue();
152            if (ignoreNull && (o == null || (o instanceof Values) && ((Values) o).isNull())) {
153                continue;
154            }
155            builder.field(k.getKey());
156            if (o instanceof Values) {
157                Values v = (Values) o;
158                v.toXContent(builder, params);
159            } else if (o instanceof Map) {
160                toXContent(builder, params, (Map<String, Object>) o);
161            } else if (o instanceof List) {
162                toXContent(builder, params, (List) o);
163            } else {
164                try {
165                    builder.value(o);
166                } catch (Throwable e) {
167                    throw new IOException("unknown object class for value:" + o.getClass().getName() + " " + o);
168                }
169            }
170        }
171        builder.endObject();
172        return builder;
173    }
174
175    /**
176     * Check if the map is empty, after optional null value removal.
177     *
178     * @param map the map to check
179     * @return true if map is empty, false if not
180     */
181    protected boolean checkCollapsedMapLength(Map<String, Object> map) {
182        int exists = 0;
183        for (Map.Entry<String, Object> k : map.entrySet()) {
184            Object o = k.getValue();
185            if (ignoreNull && (o == null || (o instanceof Values) && ((Values) o).isNull())) {
186                continue;
187            }
188            exists++;
189        }
190        return exists == 0;
191    }
192
193    @SuppressWarnings({"unchecked"})
194    protected XContentBuilder toXContent(XContentBuilder builder, Params params, List list) throws IOException {
195        builder.startArray();
196        for (Object o : list) {
197            if (o instanceof Values) {
198                Values v = (Values) o;
199                v.toXContent(builder, ToXContent.EMPTY_PARAMS);
200            } else if (o instanceof Map) {
201                toXContent(builder, params, (Map<String, Object>) o);
202            } else if (o instanceof List) {
203                toXContent(builder, params, (List) o);
204            } else {
205                try {
206                    builder.value(o);
207                } catch (Exception e) {
208                    throw new IOException("unknown object class:" + o.getClass().getName());
209                }
210            }
211        }
212        builder.endArray();
213        return builder;
214    }
215
216    @Override
217    public boolean isEmpty() {
218        return optype() == null && index() == null && type() == null && id() == null && core.isEmpty();
219    }
220
221    @Override
222    public String toString() {
223        return "[" + optype() + "/" + index() + "/" + type() + "/" + id() + "]->" + core;
224    }
225
226    @Override
227    public int compareTo(IndexableObject o) {
228        if (o == null) {
229            return -1;
230        }
231        String s1 = optype() + '/' + index() + '/' + type() + '/' + id();
232        String s2 = o.optype() + '/' + o.index() + '/' + o.type() + '/' + o.id();
233        return s1.compareTo(s2);
234    }
235
236    @Override
237    public boolean equals(Object o) {
238        return o != null && o instanceof IndexableObject && hashCode() == o.hashCode();
239    }
240
241    @Override
242    public int hashCode() {
243        int hash = 3;
244        hash = 37 * hash + (optype() != null ? optype().hashCode() : 0);
245        hash = 37 * hash + (index() != null ? index().hashCode() : 0);
246        hash = 37 * hash + (type() != null ? type().hashCode() : 0);
247        hash = 37 * hash + (id() != null ? id().hashCode() : 0);
248        return hash;
249    }
250
251}