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 */
020package org.sonar.search.script;
021
022import org.elasticsearch.common.Nullable;
023import org.elasticsearch.common.xcontent.support.XContentMapValues;
024import org.elasticsearch.script.AbstractExecutableScript;
025import org.elasticsearch.script.ExecutableScript;
026import org.elasticsearch.script.NativeScriptFactory;
027import org.sonar.process.ProcessConstants;
028
029import java.util.ArrayList;
030import java.util.Collection;
031import java.util.List;
032import java.util.Map;
033
034public 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}