/*
* Licensed to CRATE Technology GmbH ("Crate") under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. Crate 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.
*
* However, if you have executed another commercial license agreement
* with Crate these terms will supersede the license and you may use the
* software solely pursuant to the terms of the relevant commercial agreement.
*/
package io.crate.exceptions;
import com.google.common.base.MoreObjects;
import com.google.common.util.concurrent.UncheckedExecutionException;
import io.crate.action.sql.SQLActionException;
import io.crate.metadata.PartitionName;
import io.crate.sql.parser.ParsingException;
import org.elasticsearch.ElasticsearchException;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.index.engine.VersionConflictEngineException;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.shard.IllegalIndexShardStateException;
import org.elasticsearch.index.shard.ShardNotFoundException;
import org.elasticsearch.indices.IndexAlreadyExistsException;
import org.elasticsearch.indices.InvalidIndexNameException;
import org.elasticsearch.indices.InvalidIndexTemplateException;
import org.elasticsearch.repositories.RepositoryMissingException;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.snapshots.SnapshotCreationException;
import org.elasticsearch.snapshots.SnapshotMissingException;
import org.elasticsearch.transport.TransportException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Locale;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
public class SQLExceptions {
private final static Logger LOGGER = Loggers.getLogger(SQLExceptions.class);
private final static Predicate<Throwable> EXCEPTIONS_TO_UNWRAP = throwable ->
throwable instanceof TransportException ||
throwable instanceof UncheckedExecutionException ||
throwable instanceof CompletionException ||
throwable instanceof UncategorizedExecutionException ||
throwable instanceof ExecutionException;
public static Throwable unwrap(@Nonnull Throwable t, @Nullable Predicate<Throwable> additionalUnwrapCondition) {
int counter = 0;
Throwable result = t;
Predicate<Throwable> unwrapCondition = EXCEPTIONS_TO_UNWRAP;
if (additionalUnwrapCondition != null) {
unwrapCondition = unwrapCondition.or(additionalUnwrapCondition);
}
while (unwrapCondition.test(result)) {
Throwable cause = result.getCause();
if (cause == null) {
return result;
}
if (cause == result) {
return result;
}
if (counter > 10) {
LOGGER.warn("Exception cause unwrapping ran for 10 levels. Aborting unwrap", t);
return result;
}
counter++;
result = cause;
}
return result;
}
public static Throwable unwrap(@Nonnull Throwable t) {
return unwrap(t, null);
}
public static String messageOf(@Nullable Throwable t) {
if (t == null) {
return "Unknown";
}
@SuppressWarnings("all") // throwable not thrown
Throwable unwrappedT = unwrap(t);
return MoreObjects.firstNonNull(unwrappedT.getMessage(), unwrappedT.toString());
}
public static boolean isShardFailure(Throwable e) {
e = SQLExceptions.unwrap(e);
return e instanceof ShardNotFoundException || e instanceof IllegalIndexShardStateException;
}
/**
* Create a {@link SQLActionException} out of a {@link Throwable}.
* If concrete {@link ElasticsearchException} is found, first transform it
* to a {@link CrateException}
*/
public static SQLActionException createSQLActionException(Throwable e) {
// ideally this method would be a static factory method in SQLActionException,
// but that would pull too many dependencies for the client
if (e instanceof SQLActionException) {
return (SQLActionException) e;
}
e = esToCrateException(e);
int errorCode = 5000;
RestStatus restStatus = RestStatus.INTERNAL_SERVER_ERROR;
if (e instanceof CrateException) {
CrateException crateException = (CrateException) e;
if (e instanceof ValidationException) {
errorCode = 4000 + crateException.errorCode();
restStatus = RestStatus.BAD_REQUEST;
} else if (e instanceof ReadOnlyException) {
errorCode = 4030 + crateException.errorCode();
restStatus = RestStatus.FORBIDDEN;
} else if (e instanceof ResourceUnknownException) {
errorCode = 4040 + crateException.errorCode();
restStatus = RestStatus.NOT_FOUND;
} else if (e instanceof ConflictException) {
errorCode = 4090 + crateException.errorCode();
restStatus = RestStatus.CONFLICT;
} else if (e instanceof UnhandledServerException) {
errorCode = 5000 + crateException.errorCode();
}
} else if (e instanceof ParsingException) {
errorCode = 4000;
restStatus = RestStatus.BAD_REQUEST;
} else if (e instanceof MapperParsingException) {
errorCode = 4000;
restStatus = RestStatus.BAD_REQUEST;
}
String message = e.getMessage();
if (message == null) {
if (e instanceof CrateException && e.getCause() != null) {
e = e.getCause(); // use cause because it contains a more meaningful error in most cases
}
StackTraceElement[] stackTraceElements = e.getStackTrace();
if (stackTraceElements.length > 0) {
message = String.format(Locale.ENGLISH, "%s in %s", e.getClass().getSimpleName(), stackTraceElements[0]);
} else {
message = "Error in " + e.getClass().getSimpleName();
}
} else {
message = e.getClass().getSimpleName() + ": " + message;
}
return new SQLActionException(message, errorCode, restStatus, e.getStackTrace());
}
private static Throwable esToCrateException(Throwable e) {
e = SQLExceptions.unwrap(e);
if (e instanceof IllegalArgumentException || e instanceof ParsingException) {
return new SQLParseException(e.getMessage(), (Exception) e);
} else if (e instanceof UnsupportedOperationException) {
return new UnsupportedFeatureException(e.getMessage(), (Exception) e);
} else if (e instanceof VersionConflictEngineException
&& e.getMessage().contains("document already exists")) {
return new DuplicateKeyException(
"A document with the same primary key exists already", e);
} else if (e instanceof IndexAlreadyExistsException) {
return new TableAlreadyExistsException(((IndexAlreadyExistsException) e).getIndex().getName(), e);
} else if ((e instanceof InvalidIndexNameException)) {
if (e.getMessage().contains("already exists as alias")) {
// treat an alias like a table as aliases are not officially supported
return new TableAlreadyExistsException(((InvalidIndexNameException) e).getIndex().getName(),
e);
}
return new InvalidTableNameException(((InvalidIndexNameException) e).getIndex().getName(), e);
} else if (e instanceof InvalidIndexTemplateException) {
PartitionName partitionName = PartitionName.fromIndexOrTemplate(((InvalidIndexTemplateException) e).name());
return new InvalidTableNameException(partitionName.tableIdent().fqn(), e);
} else if (e instanceof IndexNotFoundException) {
return new TableUnknownException(((IndexNotFoundException) e).getIndex().getName(), e);
} else if (e instanceof org.elasticsearch.common.breaker.CircuitBreakingException) {
return new CircuitBreakingException(e.getMessage());
} else if (e instanceof InterruptedException) {
return new JobKilledException();
} else if (e instanceof RepositoryMissingException) {
return new RepositoryUnknownException(((RepositoryMissingException) e).repository());
} else if (e instanceof SnapshotMissingException) {
SnapshotMissingException snapshotException = (SnapshotMissingException) e;
return new SnapshotUnknownException(snapshotException.getRepositoryName(), snapshotException.getSnapshotName(), e);
} else if (e instanceof SnapshotCreationException) {
SnapshotCreationException creationException = (SnapshotCreationException) e;
return new SnapshotAlreadyExistsExeption(creationException.getRepositoryName(), creationException.getSnapshotName());
}
return e;
}
}