/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.solr.common.util; import java.io.IOException; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.apache.solr.common.SolrException; import org.noggit.JSONParser; import org.noggit.ObjectBuilder; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.apache.solr.common.util.StrUtils.formatString; import static org.apache.solr.common.util.Utils.toJSON; public class CommandOperation { public final String name; private Object commandData;//this is most often a map private List<String> errors = new ArrayList<>(); public CommandOperation(String operationName, Object metaData) { commandData = metaData; this.name = operationName; } public Object getCommandData() { return commandData; } public String getStr(String key, String def) { if (ROOT_OBJ.equals(key)) { Object obj = getRootPrimitive(); return obj == def ? null : String.valueOf(obj); } Object o = getMapVal(key); return o == null ? def : String.valueOf(o); } public boolean getBoolean(String key, boolean def) { String v = getStr(key,null); return v == null? def:Boolean.parseBoolean(v); } public void setCommandData(Object o){ commandData = o; } public Map<String,Object> getDataMap() { if (commandData instanceof Map) { //noinspection unchecked return (Map<String,Object>)commandData; } addError(StrUtils.formatString("The command ''{0}'' should have the values as a json object {key:val} format", name)); return Collections.emptyMap(); } private Object getRootPrimitive() { if (commandData instanceof Map) { errors.add(StrUtils.formatString("The value has to be a string for command : ''{0}'' ", name)); return null; } return commandData; } public Object getVal(String key) { return getMapVal(key); } private Object getMapVal(String key) { if("".equals(key)){ if (commandData instanceof Map) { addError("value of the command is an object should be primitive"); } return commandData; } if (commandData instanceof Map) { Map metaData = (Map) commandData; return metaData.get(key); } else { String msg = " value has to be an object for operation :" + name; if (!errors.contains(msg)) errors.add(msg); return null; } } public List<String> getStrs(String key) { List<String> val = getStrs(key, null); if (val == null) { errors.add(StrUtils.formatString(REQD, key)); } return val; } public void unknownOperation() { addError(formatString("Unknown operation ''{0}'' ", name)); } static final String REQD = "''{0}'' is a required field"; /** * Get collection of values for a key. If only one val is present a * single value collection is returned */ public List<String> getStrs(String key, List<String> def) { Object v = null; if (ROOT_OBJ.equals(key)) { v = getRootPrimitive(); } else { v = getMapVal(key); } if (v == null) { return def; } else { if (v instanceof List) { ArrayList<String> l = new ArrayList<>(); for (Object o : (List) v) { l.add(String.valueOf(o)); } if (l.isEmpty()) return def; return l; } else { return singletonList(String.valueOf(v)); } } } /** * Get a required field. If missing it adds to the errors */ public String getStr(String key) { if (ROOT_OBJ.equals(key)) { Object obj = getRootPrimitive(); if (obj == null) { errors.add(StrUtils.formatString(REQD, name)); } return obj == null ? null : String.valueOf(obj); } String s = getStr(key, null); if (s == null) errors.add(StrUtils.formatString(REQD, key)); return s; } private Map errorDetails() { return Utils.makeMap(name, commandData, ERR_MSGS, errors); } public boolean hasError() { return !errors.isEmpty(); } public void addError(String s) { if (errors.contains(s)) return; errors.add(s); } /** * Get all the values from the metadata for the command * without the specified keys */ public Map<String,Object> getValuesExcluding(String... keys) { getMapVal(null); if (hasError()) return emptyMap();//just to verify the type is Map @SuppressWarnings("unchecked") LinkedHashMap<String, Object> cp = new LinkedHashMap<>((Map<String, Object>) commandData); if (keys == null) return cp; for (String key : keys) { cp.remove(key); } return cp; } public List<String> getErrors() { return errors; } public static final String ERR_MSGS = "errorMessages"; public static final String ROOT_OBJ = ""; public static List<Map> captureErrors(List<CommandOperation> ops) { List<Map> errors = new ArrayList<>(); for (CommandOperation op : ops) { if (op.hasError()) { errors.add(op.errorDetails()); } } return errors; } /** * Parse the command operations into command objects */ public static List<CommandOperation> parse(Reader rdr) throws IOException { JSONParser parser = new JSONParser(rdr); ObjectBuilder ob = new ObjectBuilder(parser); if (parser.lastEvent() != JSONParser.OBJECT_START) { throw new RuntimeException("The JSON must be an Object of the form {\"command\": {...},..."); } List<CommandOperation> operations = new ArrayList<>(); for (; ; ) { int ev = parser.nextEvent(); if (ev == JSONParser.OBJECT_END) return operations; Object key = ob.getKey(); ev = parser.nextEvent(); Object val = ob.getVal(); if (val instanceof List) { List list = (List) val; for (Object o : list) { if (!(o instanceof Map)) { operations.add(new CommandOperation(String.valueOf(key), list)); break; } else { operations.add(new CommandOperation(String.valueOf(key), o)); } } } else { operations.add(new CommandOperation(String.valueOf(key), val)); } } } public CommandOperation getCopy() { return new CommandOperation(name, commandData); } public Map getMap(String key, Map def) { Object o = getMapVal(key); if (o == null) return def; if (!(o instanceof Map)) { addError(StrUtils.formatString("''{0}'' must be a map", key)); return def; } else { return (Map) o; } } @Override public String toString() { return new String(toJSON(singletonMap(name, commandData)), StandardCharsets.UTF_8); } public static List<CommandOperation> readCommands(Iterable<ContentStream> streams, NamedList resp) throws IOException { if (streams == null) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "missing content stream"); } ArrayList<CommandOperation> ops = new ArrayList<>(); for (ContentStream stream : streams) ops.addAll(parse(stream.getReader())); List<Map> errList = CommandOperation.captureErrors(ops); if (!errList.isEmpty()) { resp.add(CommandOperation.ERR_MSGS, errList); return null; } return ops; } public static List<CommandOperation> clone(List<CommandOperation> ops) { List<CommandOperation> opsCopy = new ArrayList<>(ops.size()); for (CommandOperation op : ops) opsCopy.add(op.getCopy()); return opsCopy; } public Integer getInt(String name, Integer def) { Object o = getVal(name); if (o == null) return def; if (o instanceof Number) { Number number = (Number) o; return number.intValue(); } else { try { return Integer.parseInt(o.toString()); } catch (NumberFormatException e) { addError(StrUtils.formatString("{0} is not a valid integer", name)); return null; } } } public Integer getInt(String name) { Object o = getVal(name); if(o == null) return null; return getInt(name, null); } }