mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Supports loading of corrupt beans
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user