diff --git a/core/src/main/java/org/elasticsearch/ElasticsearchException.java b/core/src/main/java/org/elasticsearch/ElasticsearchException.java index 362ec91d533..16b6df065a8 100644 --- a/core/src/main/java/org/elasticsearch/ElasticsearchException.java +++ b/core/src/main/java/org/elasticsearch/ElasticsearchException.java @@ -26,6 +26,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.NotSerializableExceptionWrapper; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.rest.HasRestHeaders; @@ -42,7 +43,7 @@ import java.util.*; public class ElasticsearchException extends RuntimeException implements ToXContent { public static final String REST_EXCEPTION_SKIP_CAUSE = "rest.exception.skip_cause"; - static final Map> MAPPING; + private static final Map> MAPPING; /** * Construct a ElasticsearchException with the specified detail message. @@ -177,6 +178,17 @@ public class ElasticsearchException extends RuntimeException implements ToXConte } } + /** + * Retruns true iff the given name is a registered for an exception to be read. + */ + static boolean isRegistered(String name) { + return MAPPING.containsKey(name); + } + + static Set getRegisteredKeys() { // for testing + return MAPPING.keySet(); + } + /** * A base class for exceptions that should carry rest headers */ @@ -533,10 +545,9 @@ public class ElasticsearchException extends RuntimeException implements ToXConte org.elasticsearch.action.PrimaryMissingActionException.class, org.elasticsearch.index.engine.CreateFailedEngineException.class, org.elasticsearch.index.shard.IllegalIndexShardStateException.class, - org.elasticsearch.common.io.stream.StreamInput.NamedException.class + NotSerializableExceptionWrapper.class }; - Map> mapping = new HashMap<>(); - + Map> mapping = new HashMap<>(exceptions.length); for (Class e : exceptions) { String name = e.getName(); try { diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/NotSerializableExceptionWrapper.java b/core/src/main/java/org/elasticsearch/common/io/stream/NotSerializableExceptionWrapper.java new file mode 100644 index 00000000000..c77233246ed --- /dev/null +++ b/core/src/main/java/org/elasticsearch/common/io/stream/NotSerializableExceptionWrapper.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package org.elasticsearch.common.io.stream; + +import org.elasticsearch.ElasticsearchException; + +import java.io.IOException; + +/** + * This exception can be used to wrap a given, not serializable exception + * to serialize via {@link StreamOutput#writeThrowable(Throwable)} + */ +public final class NotSerializableExceptionWrapper extends ElasticsearchException { + + private final String name; + + public NotSerializableExceptionWrapper(Throwable other) { + super(other.getMessage(), other.getCause()); + this.name = ElasticsearchException.getExceptionName(other); + } + + public NotSerializableExceptionWrapper(StreamInput in) throws IOException { + super(in); + name = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(name); + } + + @Override + protected String getExceptionName() { + return name; + } +} diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java index f2d5416a6d0..e8e3f6f6d20 100644 --- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java +++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java @@ -546,33 +546,4 @@ public abstract class StreamInput extends InputStream { return new InputStreamStreamInput(new ByteArrayInputStream(bytes, offset, length)); } - public static class NamedException extends ElasticsearchException { - - private final String name; - - public NamedException(String name, String msg, Throwable cause) { - super(msg, cause); - if (name == null) { - throw new IllegalArgumentException("name must not be null"); - } - this.name = name; - } - - public NamedException(StreamInput in) throws IOException { - super(in); - name = in.readString(); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(name); - } - - @Override - protected String getExceptionName() { - return Strings.toUnderscoreCase(name); - } - } - } diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java index 57a0585526d..da6ab25e65f 100644 --- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java +++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java @@ -26,7 +26,6 @@ import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefBuilder; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.Version; -import org.elasticsearch.bootstrap.Elasticsearch; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.text.Text; @@ -34,7 +33,6 @@ import org.joda.time.ReadableInstant; import java.io.EOFException; import java.io.IOException; -import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.Date; import java.util.LinkedHashMap; @@ -478,7 +476,7 @@ public abstract class StreamOutput extends OutputStream { if (throwable instanceof ElasticsearchException) { ex = (ElasticsearchException) throwable; } else { - ex = new StreamInput.NamedException(ElasticsearchException.getExceptionName(throwable), throwable.getMessage(), throwable.getCause()); + ex = new NotSerializableExceptionWrapper(throwable); } writeVInt(0); writeString(ex.getClass().getName()); diff --git a/core/src/test/java/org/elasticsearch/ExceptionSerializationTests.java b/core/src/test/java/org/elasticsearch/ExceptionSerializationTests.java index 8c0088a78cf..fa25846c12e 100644 --- a/core/src/test/java/org/elasticsearch/ExceptionSerializationTests.java +++ b/core/src/test/java/org/elasticsearch/ExceptionSerializationTests.java @@ -36,7 +36,7 @@ import org.elasticsearch.common.io.stream.*; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.LocalTransportAddress; import org.elasticsearch.common.unit.ByteSizeValue; -import org.elasticsearch.common.xcontent.XContentLocation; +import org.elasticsearch.common.xcontent.*; import org.elasticsearch.discovery.DiscoverySettings; import org.elasticsearch.index.AlreadyExpiredException; import org.elasticsearch.index.Index; @@ -115,9 +115,9 @@ public class ExceptionSerializationTests extends ElasticsearchTestCase { Class clazz = loadClass(filename); if (ignore.contains(clazz) == false) { if (Modifier.isAbstract(clazz.getModifiers()) == false && Modifier.isInterface(clazz.getModifiers()) == false && isEsException(clazz)) { - if (ElasticsearchException.MAPPING.containsKey(clazz.getName()) == false && ElasticsearchException.class.equals(clazz.getEnclosingClass()) == false) { + if (ElasticsearchException.isRegistered(clazz.getName()) == false && ElasticsearchException.class.equals(clazz.getEnclosingClass()) == false) { notRegistered.add(clazz); - } else if (ElasticsearchException.MAPPING.containsKey(clazz.getName())) { + } else if (ElasticsearchException.isRegistered(clazz.getName())) { registered.add(clazz.getName()); try { if (clazz.getDeclaredMethod("writeTo", StreamOutput.class) != null) { @@ -169,7 +169,7 @@ public class ExceptionSerializationTests extends ElasticsearchTestCase { assertTrue(notRegistered.remove(TestException.class)); assertTrue("Classes subclassing ElasticsearchException must be registered \n" + notRegistered.toString(), notRegistered.isEmpty()); - assertTrue(registered.removeAll(ElasticsearchException.MAPPING.keySet())); // check + assertTrue(registered.removeAll(ElasticsearchException.getRegisteredKeys())); // check assertEquals(registered.toString(), 0, registered.size()); } @@ -535,4 +535,22 @@ public class ExceptionSerializationTests extends ElasticsearchTestCase { assertNull(ex.index()); assertNull(ex.shardId()); } + private String toXContent(ToXContent x) { + try { + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + x.toXContent(builder, ToXContent.EMPTY_PARAMS); + builder.endObject(); + return builder.string(); + } catch (IOException e) { + return "{ \"error\" : \"" + e.getMessage() + "\"}"; + } + } + + public void testNotSerializableExceptionWrapper() throws IOException { + NotSerializableExceptionWrapper ex = serialize(new NotSerializableExceptionWrapper(new NullPointerException())); + assertEquals("{\"type\":\"null_pointer_exception\",\"reason\":null}", toXContent(ex)); + ex = serialize(new NotSerializableExceptionWrapper(new IllegalArgumentException("nono!"))); + assertEquals("{\"type\":\"illegal_argument_exception\",\"reason\":\"nono!\"}", toXContent(ex)); + } }