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;
027
028import java.util.ArrayList;
029import java.util.Collection;
030import java.util.List;
031import java.util.Map;
032
033public 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}