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    }