Supports loading of corrupt beans

This commit is contained in:
Roland Praml
2019-01-03 11:43:50 +01:00
parent 0b299edf0d
commit 4d93b70bf9
26 changed files with 221 additions and 24 deletions
+8
View File
@@ -3,6 +3,8 @@ package io.ebean;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Provides access to the internal state of an entity bean.
*/
@@ -111,4 +113,10 @@ public interface BeanState {
* Reset the bean putting it into NEW state such that a save() results in an insert.
*/
void resetForInsert();
/**
* Returns a map with load erros.
*/
@Nullable
Map<String, Exception> getLoadErrors();
}
@@ -98,6 +98,8 @@ public final class EntityBeanIntercept implements Serializable {
private Object[] origValues;
private Exception[] loadErrors;
private int lazyLoadProperty = -1;
private Object ownerId;
@@ -1115,4 +1117,36 @@ public final class EntityBeanIntercept implements Serializable {
public void setSortOrder(int sortOrder) {
this.sortOrder = sortOrder;
}
/**
* Set the load error that happened on this property.
*/
public void setLoadError(int propertyIndex, Exception t) {
if (loadErrors == null) {
loadErrors = new Exception[owner._ebean_getPropertyNames().length];
}
loadErrors[propertyIndex] = t;
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
/**
* Returns the loadErrors.
*/
public Map<String, Exception> getLoadErrors() {
if (loadErrors == null) {
return null;
}
Map<String, Exception> ret = null;
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
Exception loadError = loadErrors[i];
if (loadError != null) {
if (ret == null) {
ret = new LinkedHashMap<>();
}
ret.put(getProperty(i), loadError);
}
}
return ret;
}
}
@@ -30,9 +30,15 @@ import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.migration.MigrationRunner;
import io.ebean.plugin.LoadErrorHandler;
import io.ebean.util.StringHelper;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Collections;
@@ -518,6 +524,10 @@ public class ServerConfig {
*/
private boolean idGeneratorAutomatic = true;
private LoadErrorHandler loadErrorHandler = (bean, prop, fullName, e) -> {
throw new PersistenceException("Error loading on " + fullName, e);
};
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -2985,6 +2995,13 @@ public class ServerConfig {
String mappingsProp = p.get("mappingLocations", null);
mappingLocations = getSearchList(mappingsProp, mappingLocations);
if (!p.getBoolean("failOnLoadError", true)) {
Logger logger = LoggerFactory.getLogger("io.ebean.SQL");
loadErrorHandler = (bean, prop, fullName, e) -> {
logger.error("Error loading on {}", fullName, e);
};
}
}
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
@@ -3244,6 +3261,20 @@ public class ServerConfig {
this.idGeneratorAutomatic = idGeneratorAutomatic;
}
/**
* Returns the load error handler.
*/
public LoadErrorHandler getLoadErrorHandler() {
return loadErrorHandler;
}
/**
* Sets the loadErrorHandler.
*/
public void setLoadErrorHandler(LoadErrorHandler loadErrorHandler) {
this.loadErrorHandler = loadErrorHandler;
}
/**
* Return true if query plan capture is enabled.
*/
@@ -0,0 +1,14 @@
package io.ebean.plugin;
import io.ebean.bean.EntityBean;
/**
* Errorhandler to handle load errors and may be recover correct value.
*
* @author Roland Praml, FOCONIS AG
*
*/
@FunctionalInterface
public interface LoadErrorHandler {
void handleLoadError(EntityBean bean, Property prop, String fullName, Exception e);
}
@@ -6,6 +6,7 @@ package io.ebean.text;
public class TextException extends RuntimeException {
private static final long serialVersionUID = 1601310159486033148L;
private String text;
/**
* Construct with an error message.
@@ -27,4 +28,41 @@ public class TextException extends RuntimeException {
public TextException(Exception e) {
super(e);
}
/**
* Constructor for a detailed exception.
*
* @param message
* the message. The placeholder {} will be replaced by
* <code>text</code>
* @param text
* the fault text.
* @param cause
* the case
*/
public TextException(String message, String text, Exception cause) {
super(message.replace("{}", String.valueOf(text)), cause);
this.text = text;
}
/**
* Constructor for a detailed exception.
*
* @param message
* the message. The placeholder {} will be replaced by
* <code>text</code>
* @param text
* the fault text.
*/
public TextException(String message, String text) {
super(message.replace("{}", String.valueOf(text)));
this.text = text;
}
/**
* Return the text, that caused the error. (e.g. the JSON). May be null.
*/
public String getText() {
return text;
}
}
@@ -8,6 +8,7 @@ import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.bean.CallStack;
import io.ebean.bean.EntityBean;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.readaudit.ReadEvent;
@@ -15,6 +16,7 @@ import io.ebean.plugin.BeanType;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.query.CancelableQuery;
@@ -844,4 +846,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
* Returns the count distinct order setting.
*/
CountDistinctOrder getCountDistinctOrder();
/**
* Handles load errors.
*/
void handleLoadError(EntityBean bean, BeanProperty prop, String fullName, Exception e);
}
@@ -88,4 +88,10 @@ public class DefaultBeanState implements BeanState {
public void resetForInsert() {
intercept.setNew();
}
@Override
public Map<String, Exception> getLoadErrors() {
return intercept.getLoadErrors();
}
}
@@ -90,4 +90,10 @@ public interface DbReadContext {
* Return true if this request disables lazy loading.
*/
boolean isDisableLazyLoading();
/**
* Handles a load error on given property.
*/
void handleLoadError(EntityBean bean, BeanProperty prop, String fullName, Exception e);
}
@@ -49,15 +49,16 @@ class DynamicPropertyAggregationFormula extends DynamicPropertyBase {
@Override
public void load(SqlBeanLoad sqlBeanLoad) {
Object value;
try {
Object value = scalarType.read(sqlBeanLoad.ctx().getDataReader());
if (asTarget != null) {
sqlBeanLoad.load(asTarget, value);
}
value = scalarType.read(sqlBeanLoad.ctx().getDataReader());
} catch (Exception e) {
throw new PersistenceException("Error loading on " + fullName, e);
sqlBeanLoad.ctx().handleLoadError(null, asTarget, fullName, e);
return;
}
if (asTarget != null) {
sqlBeanLoad.load(asTarget, value);
}
}
@@ -21,6 +21,7 @@ import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanCollectionHelp;
import io.ebeaninternal.server.deploy.BeanCollectionHelpFactory;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.type.DataBind;
@@ -815,6 +816,11 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
return pstmt;
}
@Override
public void handleLoadError(EntityBean bean, BeanProperty prop, String fullName, Exception e) {
query.handleLoadError(bean, prop, fullName, e);
}
public Set<String> getDependentTables() {
return queryPlan.getDependentTables();
}
@@ -6,8 +6,6 @@ import io.ebeaninternal.api.SpiQuery.Mode;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.DbReadContext;
import javax.persistence.PersistenceException;
/**
* Controls the loading of property data into a bean.
* <p>
@@ -88,8 +86,9 @@ public class SqlBeanLoad {
return dbVal;
} catch (Exception e) {
String msg = "Error loading on " + prop.getFullBeanName();
throw new PersistenceException(msg, e);
bean._ebean_getIntercept().setLoadError(prop.getPropertyIndex(), e);
ctx.handleLoadError(bean, prop, prop.getFullBeanName(), e);
return prop.getValue(bean);
}
}
@@ -25,6 +25,7 @@ import io.ebean.Transaction;
import io.ebean.UpdateQuery;
import io.ebean.Version;
import io.ebean.bean.CallStack;
import io.ebean.bean.EntityBean;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.ObjectGraphOrigin;
import io.ebean.bean.PersistenceContext;
@@ -46,6 +47,7 @@ import io.ebeaninternal.api.SpiQuerySecondary;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.expression.DefaultExpressionList;
@@ -1921,6 +1923,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return profileLocation;
}
@Override
public void handleLoadError(EntityBean bean, BeanProperty prop, String fullName, Exception e) {
server.getServerConfig().getLoadErrorHandler().handleLoadError(bean, prop, fullName, e);
}
@Override
public Query<T> orderById(boolean orderById) {
this.orderById = orderById;
@@ -351,7 +351,7 @@ public class TCsvReader<T> implements CsvReader<T> {
return path.parseDateTime(dt.getTime());
} catch (ParseException e) {
throw new TextException("Error parsing [" + value + "] using format[" + format + "]", e);
throw new TextException("Error parsing [{}] using format[" + format + "]", value, e);
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.text.TextException;
import io.ebean.text.json.EJson;
import io.ebeaninternal.json.ModifyAwareList;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
@@ -145,7 +146,7 @@ public class ScalarTypeArrayList extends ScalarTypeJsonCollection<List> implemen
try {
return EJson.parseList(value, false);
} catch (IOException e) {
throw new PersistenceException("Failed to parse JSON content as List: [" + value + "]", e);
throw new TextException("Failed to parse JSON [{}] as List", value, e);
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.text.TextException;
import io.ebean.text.json.EJson;
import io.ebeaninternal.json.ModifyAwareSet;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
@@ -144,7 +145,7 @@ public class ScalarTypeArraySet<T> extends ScalarTypeJsonCollection<Set<T>> impl
try {
return EJson.parseSet(value, false);
} catch (IOException e) {
throw new PersistenceException("Failed to parse JSON content as List: [" + value + "]", e);
throw new TextException("Failed to parse JSON [{}] as Set", value, e);
}
}
@@ -42,7 +42,7 @@ public class ScalarTypeInetAddress extends ScalarTypeBaseVarchar<InetAddress> {
try {
return ConvertInetAddresses.forUriString(value);
} catch (IllegalArgumentException e) {
throw new TextException("Error with InetAddresses [" + value + "] ", e);
throw new TextException("Error with InetAddresses [{}]", value, e);
}
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.type;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.text.TextException;
import io.ebean.text.json.EJson;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -75,7 +76,7 @@ public class ScalarTypeJsonList {
// parse JSON into modifyAware list
return EJson.parseList(json, true);
} catch (IOException e) {
throw new SQLException("Failed to parse JSON content as List: [" + json + "]", e);
throw new TextException("Failed to parse JSON [{}] as List", json, e);
}
}
@@ -109,7 +110,7 @@ public class ScalarTypeJsonList {
try {
return EJson.parseList(value, false);
} catch (IOException e) {
throw new PersistenceException("Failed to parse JSON content as List: [" + value + "]", e);
throw new TextException("Failed to parse JSON [{}] as List", value, e);
}
}
@@ -175,7 +175,7 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase<Map> {
// return a modify aware map
return EJson.parseObject(value, true);
} catch (IOException e) {
throw new TextException(e);
throw new TextException("Failed to parse JSON [{}] as Object", value, e);
}
}
@@ -158,7 +158,7 @@ public abstract class ScalarTypeJsonNode extends ScalarTypeBase<JsonNode> {
try {
return objectMapper.readValue(value, JsonNode.class);
} catch (IOException e) {
throw new TextException(e);
throw new TextException("Failed to parse JSON [{}] as JsonNode", value, e);
}
}
@@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.text.TextException;
import io.ebeaninternal.json.ModifyAwareList;
import io.ebeaninternal.json.ModifyAwareMap;
import io.ebeaninternal.json.ModifyAwareOwner;
@@ -208,7 +209,7 @@ public class ScalarTypeJsonObjectMapper {
try {
return objectReader.readValue(json, deserType);
} catch (IOException e) {
throw new SQLException("Unable to convert JSON", e);
throw new TextException("Failed to parse JSON [{}] as " + deserType, json, e);
}
}
@@ -258,7 +259,7 @@ public class ScalarTypeJsonObjectMapper {
try {
return objectReader.readValue(value, deserType);
} catch (IOException e) {
throw new PersistenceException("Unable to convert JSON", e);
throw new TextException("Failed to parse JSON [{}] as " + deserType, value, e);
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.type;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.text.TextException;
import io.ebean.text.json.EJson;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -78,7 +79,7 @@ public class ScalarTypeJsonSet {
// parse JSON into modifyAware list
return EJson.parseSet(json, true);
} catch (IOException e) {
throw new SQLException("Failed to parse JSON content as List: [" + json + "]", e);
throw new TextException("Failed to parse JSON [{}] as Set", json, e);
}
}
@@ -78,7 +78,7 @@ public class ScalarTypePostgresHstore extends ScalarTypeBase<Map> {
try {
return EJson.parseObject(value);
} catch (IOException e) {
throw new TextException(e);
throw new TextException("Failed to parse JSON [{}] as Object", value, e);
}
}
@@ -38,7 +38,7 @@ public class ScalarTypeURI extends ScalarTypeBaseVarchar<URI> {
try {
return new URI(value);
} catch (URISyntaxException e) {
throw new TextException("Error with URI [" + value + "] ", e);
throw new TextException("Error with URI [{}]", value, e);
}
}
}
@@ -38,7 +38,7 @@ public class ScalarTypeURL extends ScalarTypeBaseVarchar<URL> {
try {
return new URL(value);
} catch (MalformedURLException e) {
throw new TextException(e);
throw new TextException("Error with URL [{}]", value, e);
}
}