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}