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 import org.sonar.process.ProcessConstants;
028
029 import java.util.ArrayList;
030 import java.util.Collection;
031 import java.util.List;
032 import java.util.Map;
033
034 public class ListUpdate extends AbstractExecutableScript {
035
036 public static class UpdateListScriptFactory implements NativeScriptFactory {
037 @Override
038 public ExecutableScript newScript(@Nullable Map<String, Object> params) {
039 String idField = XContentMapValues.nodeStringValue(params.get(ProcessConstants.ES_PLUGIN_LISTUPDATE_ID_FIELD), null);
040 String idValue = XContentMapValues.nodeStringValue(params.get(ProcessConstants.ES_PLUGIN_LISTUPDATE_ID_VALUE), null);
041 String field = XContentMapValues.nodeStringValue(params.get(ProcessConstants.ES_PLUGIN_LISTUPDATE_FIELD), null);
042 Map value = null;
043 if (idField == null) {
044 throw new IllegalStateException(String.format("Missing '%s' parameter", ProcessConstants.ES_PLUGIN_LISTUPDATE_ID_FIELD));
045 }
046 if (idValue == null) {
047 throw new IllegalStateException(String.format("Missing '%s' parameter", ProcessConstants.ES_PLUGIN_LISTUPDATE_ID_VALUE));
048 }
049 if (field == null) {
050 throw new IllegalStateException(String.format("Missing '%s' parameter", ProcessConstants.ES_PLUGIN_LISTUPDATE_FIELD));
051 }
052
053 //NULL case is deletion of nested item
054 if (params.containsKey(ProcessConstants.ES_PLUGIN_LISTUPDATE_VALUE)) {
055 Object obj = params.get(ProcessConstants.ES_PLUGIN_LISTUPDATE_VALUE);
056 if (obj != null) {
057 value = XContentMapValues.nodeMapValue(params.get(ProcessConstants.ES_PLUGIN_LISTUPDATE_VALUE), "Update item");
058 }
059 }
060
061 return new ListUpdate(idField, idValue, field, value);
062 }
063 }
064
065
066 private final String idField;
067 private final String idValue;
068 private final String field;
069 private final Map<String, Object> value;
070 private Map<String, Object> ctx;
071
072 public ListUpdate(String idField, String idValue, String field, @Nullable Map<String, Object> value) {
073 this.idField = idField;
074 this.idValue = idValue;
075 this.field = field;
076 this.value = value;
077 }
078
079 @Override
080 public void setNextVar(String name, Object value) {
081 if ("ctx".equals(name)) {
082 ctx = (Map<String, Object>) value;
083 }
084 }
085
086 @Override
087 public Object unwrap(Object value) {
088 return value;
089 }
090
091 @Override
092 public Object run() {
093 try {
094 //Get the Document's source from ctx
095 Map<String, Object> source = XContentMapValues.nodeMapValue(ctx.get("_source"), "source from context");
096
097 //Get the Object for list update
098 Object fieldValue = source.get(field);
099
100 if (fieldValue == null && value != null) {
101 // 0. The field does not exist (this is a upsert then)
102 List values = new ArrayList<Object>(1);
103 values.add(value);
104 source.put(field, values);
105 } else if (!XContentMapValues.isArray(fieldValue) && value != null) {
106 // 1. The field is not yet a list
107 Map currentFieldValue = XContentMapValues.nodeMapValue(fieldValue, "current FieldValue");
108 if (XContentMapValues.nodeStringValue(currentFieldValue.get(idField), null).equals(idValue)) {
109 source.put(field, value);
110 } else {
111 List values = new ArrayList<Object>(2);
112 values.add(fieldValue);
113 values.add(value);
114 source.put(field, values);
115 }
116 } else {
117 // 3. field is a list
118 Collection items = (Collection) fieldValue;
119 Object target = null;
120 for (Object item : items) {
121 Map<String, Object> fields = (Map<String, Object>) item;
122 String itemIdValue = XContentMapValues.nodeStringValue(fields.get(idField), null);
123 if (itemIdValue != null && itemIdValue.equals(idValue)) {
124 target = item;
125 break;
126 }
127 }
128 if (target != null) {
129 items.remove(target);
130 }
131
132 //Supporting the update by NULL = deletion case
133 if (value != null) {
134 items.add(value);
135 }
136 source.put(field, items);
137 }
138 } catch (Exception e) {
139 throw new IllegalStateException("failed to execute listUpdate script", e);
140 }
141 return null;
142
143 }
144 }