/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.script; import org.opensearch.OpenSearchException; import org.opensearch.common.Strings; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.rest.RestStatus; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; /** * Exception from a scripting engine. *

* A ScriptException has the following components: *

* * @opensearch.internal */ @SuppressWarnings("serial") public class ScriptException extends OpenSearchException { private final List scriptStack; private final String script; private final String lang; private final Position pos; /** * Create a new ScriptException. * @param message A short and simple summary of what happened, such as "compile error". * Must not be {@code null}. * @param cause The underlying cause of the exception. Must not be {@code null}. * @param scriptStack An implementation-specific "stacktrace" for the error in the script. * Must not be {@code null}, but can be empty (though this should be avoided if possible). * @param script Identifier for which script failed. Must not be {@code null}. * @param lang Scripting engine language, such as "painless". Must not be {@code null}. * @param pos Position of error within script, may be {@code null}. * @throws NullPointerException if any parameters are {@code null} except pos. */ public ScriptException(String message, Throwable cause, List scriptStack, String script, String lang, Position pos) { super(Objects.requireNonNull(message), Objects.requireNonNull(cause)); this.scriptStack = Collections.unmodifiableList(Objects.requireNonNull(scriptStack)); this.script = Objects.requireNonNull(script); this.lang = Objects.requireNonNull(lang); this.pos = pos; } /** * Create a new ScriptException with null Position. */ public ScriptException(String message, Throwable cause, List scriptStack, String script, String lang) { this(message, cause, scriptStack, script, lang, null); } /** * Deserializes a ScriptException from a {@code StreamInput} */ public ScriptException(StreamInput in) throws IOException { super(in); scriptStack = Arrays.asList(in.readStringArray()); script = in.readString(); lang = in.readString(); pos = Position.readFromOptional(in); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeStringArray(scriptStack.toArray(new String[0])); out.writeString(script); out.writeString(lang); Position.writeToOptional(out, pos); } @Override protected void metadataToXContent(XContentBuilder builder, Params params) throws IOException { builder.field("script_stack", scriptStack); builder.field("script", script); builder.field("lang", lang); if (pos != null) { pos.toXContent(builder, params); } } /** * Returns the stacktrace for the error in the script. * @return a read-only list of frames, which may be empty. */ public List getScriptStack() { return scriptStack; } /** * Returns the identifier for which script. * @return script's name or source text that identifies the script. */ public String getScript() { return script; } /** * Returns the language of the script. * @return the {@code lang} parameter of the scripting engine. */ public String getLang() { return lang; } /** * Returns the position of the error. */ public Position getPos() { return pos; } /** * Returns a JSON version of this exception for debugging. */ public String toJsonString() { try { XContentBuilder json = XContentFactory.jsonBuilder().prettyPrint(); json.startObject(); toXContent(json, ToXContent.EMPTY_PARAMS); json.endObject(); return Strings.toString(json); } catch (IOException e) { throw new RuntimeException(e); } } @Override public RestStatus status() { return RestStatus.BAD_REQUEST; } /** * Position data * * @opensearch.internal */ public static class Position { public final int offset; public final int start; public final int end; public Position(int offset, int start, int end) { this.offset = offset; this.start = start; this.end = end; } Position(StreamInput in) throws IOException { offset = in.readInt(); start = in.readInt(); end = in.readInt(); } public static Position readFromOptional(StreamInput in) throws IOException { if (in.readBoolean() == true) { return new Position(in); } return null; } void writeTo(StreamOutput out) throws IOException { out.writeInt(offset); out.writeInt(start); out.writeInt(end); } public static void writeToOptional(StreamOutput out, Position object) throws IOException { boolean present = object != null; out.writeBoolean(present); if (present == true) { object.writeTo(out); } } void toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject("position"); builder.field("offset", offset); builder.field("start", start); builder.field("end", end); builder.endObject(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Position position = (Position) o; return offset == position.offset && start == position.start && end == position.end; } @Override public int hashCode() { return Objects.hash(offset, start, end); } } }