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}