001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 */
020 package org.sonar.search.script;
021
022 import org.elasticsearch.common.Nullable;
023 import org.elasticsearch.common.xcontent.support.XContentMapValues;
024 import org.elasticsearch.script.AbstractExecutableScript;
025 import org.elasticsearch.script.ExecutableScript;
026 import org.elasticsearch.script.NativeScriptFactory;
027
028 import java.util.ArrayList;
029 import java.util.Collection;
030 import java.util.List;
031 import java.util.Map;
032
033 public class ListUpdate extends AbstractExecutableScript {
034
035 public static final String NAME = "listUpdate";
036
037 public static final String ID_FIELD = "idField";
038 public static final String ID_VALUE = "idValue";
039 public static final String FIELD = "field";
040 public static final String VALUE = "value";
041
042 public static class UpdateListScriptFactory implements NativeScriptFactory {
043 @Override
044 public ExecutableScript newScript(@Nullable Map<String, Object> params) {
045 String idField = XContentMapValues.nodeStringValue(params.get(ID_FIELD), null);
046 String idValue = XContentMapValues.nodeStringValue(params.get(ID_VALUE), null);
047 String field = XContentMapValues.nodeStringValue(params.get(FIELD), null);
048 Map value = null;
049 if (idField == null) {
050 throw new IllegalStateException("Missing '" + ID_FIELD + "' parameter");
051 }
052 if (idValue == null) {
053 throw new IllegalStateException("Missing '" + ID_VALUE + "' parameter");
054 }
055 if (field == null) {
056 throw new IllegalStateException("Missing '" + FIELD + "' parameter");
057 }
058
059 //NULL case is deletion of nested item
060 if (params.containsKey(VALUE)) {
061 Object obj = params.get(VALUE);
062 if (obj != null) {
063 value = XContentMapValues.nodeMapValue(params.get(VALUE), "Update item");
064 }
065 }
066
067 return new ListUpdate(idField, idValue, field, value);
068 }
069 }
070
071
072 private final String idField;
073 private final String idValue;
074 private final String field;
075 private final Map<String, Object> value;
076
077 private Map<String, Object> ctx;
078
079 public ListUpdate(String idField, String idValue, String field, @Nullable Map<String, Object> value) {
080 this.idField = idField;
081 this.idValue = idValue;
082 this.field = field;
083 this.value = value;
084 }
085
086 @Override
087 public void setNextVar(String name, Object value) {
088 if ("ctx".equals(name)) {
089 ctx = (Map<String, Object>) value;
090 }
091 }
092
093 @Override
094 public Object unwrap(Object value) {
095 return value;
096 }
097
098 @Override
099 public Object run() {
100 try {
101 //Get the Document's source from ctx
102 Map<String, Object> source = XContentMapValues.nodeMapValue(ctx.get("_source"), "source from context");
103
104 //Get the Object for list update
105 Object fieldValue = source.get(field);
106
107 if (fieldValue == null && value != null) {
108 // 0. The field does not exist (this is a upsert then)
109 List values = new ArrayList<Object>(1);
110 values.add(value);
111 source.put(field, values);
112 } else if (!XContentMapValues.isArray(fieldValue) && value != null) {
113 // 1. The field is not yet a list
114 Map currentFieldValue = XContentMapValues.nodeMapValue(fieldValue, "current FieldValue");
115 if (XContentMapValues.nodeStringValue(currentFieldValue.get(idField), null).equals(idValue)) {
116 source.put(field, value);
117 } else {
118 List values = new ArrayList<Object>(2);
119 values.add(fieldValue);
120 values.add(value);
121 source.put(field, values);
122 }
123 } else {
124 // 3. field is a list
125 Collection items = (Collection) fieldValue;
126 Object target = null;
127 for (Object item : items) {
128 Map<String, Object> fields = (Map<String, Object>) item;
129 String itemIdValue = XContentMapValues.nodeStringValue(fields.get(idField), null);
130 if (itemIdValue != null && itemIdValue.equals(idValue)) {
131 target = item;
132 break;
133 }
134 }
135 if (target != null) {
136 items.remove(target);
137 }
138
139 //Supporting the update by NULL = deletion case
140 if (value != null) {
141 items.add(value);
142 }
143 source.put(field, items);
144 }
145 } catch (Exception e) {
146 throw new IllegalStateException("failed to execute listUpdate script", e);
147 }
148 return null;
149
150 }
151 }