Skip to content

Commit

Permalink
Merge branch 'develop' into gh-3059-federated-store-applyviewtoelemen…
Browse files Browse the repository at this point in the history
…ts-schema-validation
  • Loading branch information
GCHQDev404 committed Nov 7, 2023
2 parents 5dba0fe + ecc9f44 commit df0ec86
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 106 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2020 Crown Copyright
* Copyright 2016-2023 Crown Copyright
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,6 +27,8 @@
*/
public class SchemaException extends GafferRuntimeException {
private static final long serialVersionUID = 3150434301320173603L;
@SuppressWarnings("PMD.AvoidStringBufferField")
private StringBuilder prependToMessage;

public SchemaException(final String message) {
super(message, BAD_REQUEST);
Expand All @@ -35,4 +37,19 @@ public SchemaException(final String message) {
public SchemaException(final String message, final Throwable e) {
super(message, e, BAD_REQUEST);
}

public SchemaException prependToMessage(final String prependToMessage) {
if (null == this.prependToMessage) {
this.prependToMessage = new StringBuilder();
}
//preAppend
this.prependToMessage.insert(0, prependToMessage);

return this;
}

@Override
public String getMessage() {
return (null == prependToMessage) ? super.getMessage() : prependToMessage.append(super.getMessage()).toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@

import uk.gov.gchq.gaffer.commonutil.GroupUtil;
import uk.gov.gchq.gaffer.commonutil.ToStringBuilder;
import uk.gov.gchq.gaffer.core.exception.GafferRuntimeException;
import uk.gov.gchq.gaffer.data.elementdefinition.ElementDefinitions;
import uk.gov.gchq.gaffer.data.elementdefinition.exception.SchemaException;
import uk.gov.gchq.gaffer.exception.SerialisationException;
import uk.gov.gchq.gaffer.jsonserialisation.JSONSerialiser;
import uk.gov.gchq.gaffer.serialisation.Serialiser;
import uk.gov.gchq.gaffer.store.schema.exception.SplitElementGroupDefSchemaException;
import uk.gov.gchq.gaffer.store.schema.exception.VertexSerialiserSchemaException;
import uk.gov.gchq.gaffer.store.schema.exception.VisibilityPropertySchemaException;
import uk.gov.gchq.koryphe.ValidationResult;
import uk.gov.gchq.koryphe.iterable.ChainedIterable;

Expand All @@ -48,6 +49,7 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.Objects.nonNull;

Expand All @@ -72,7 +74,10 @@
@JsonDeserialize(builder = Schema.Builder.class)
@JsonPropertyOrder(value = {"class", "edges", "entities", "types"}, alphabetic = true)
public class Schema extends ElementDefinitions<SchemaEntityDefinition, SchemaEdgeDefinition> implements Cloneable {
public static final String UNABLE_TO_MERGE_SCHEMAS_CONFLICT_WITH_VERTEX_SERIALISER_OPTIONS_ARE = "Unable to merge schemas. Conflict with vertex serialiser, options are: ";
public static final String FORMAT_EXCEPTION = "%s, options are: %s and %s";
public static final String FORMAT_UNABLE_TO_MERGE_SCHEMAS_CONFLICT_WITH_S = "Unable to merge schemas because of conflict with the %s";
public static final String FORMAT_ERROR_WITH_THE_SCHEMA_TYPE_NAMED_S_DUE_TO_S = "Error with the schema type named:%s due to: %s";
public static final String ERROR_MERGING_SCHEMA_DUE_TO = "Error merging Schema due to: ";
private final TypeDefinition unknownType = new TypeDefinition();

/**
Expand Down Expand Up @@ -328,24 +333,27 @@ public CHILD_CLASS config(final String key, final String value) {
@JsonIgnore
public CHILD_CLASS merge(final Schema schema) {
if (nonNull(schema)) {
Schema thatSchema;
try {
thatSchema = JSONSerialiser.deserialise(JSONSerialiser.serialise(schema), Schema.class);
} catch (final SerialisationException e) {
throw new GafferRuntimeException("Error merging Schema", e);
}
Schema thatSchema = JSONSerialiser.deserialise(JSONSerialiser.serialise(schema), Schema.class);

validateSharedGroupsAreCompatible(thatSchema);

validateSharedGroupsAreCompatible(thatSchema);
mergeElements(thatSchema);

mergeElements(thatSchema);
mergeVertexSerialiser(thatSchema);

mergeVertexSerialiser(thatSchema);
mergeVisibility(thatSchema);

mergeVisibility(thatSchema);
mergeTypes(thatSchema);

mergeTypes(thatSchema);
mergeConfig(thatSchema);

mergeConfig(thatSchema);
} catch (final SchemaException e) {
e.prependToMessage(ERROR_MERGING_SCHEMA_DUE_TO);
throw e;
} catch (final Exception e) {
throw new SchemaException(ERROR_MERGING_SCHEMA_DUE_TO + e.getMessage(), e);
}
}

return self();
Expand Down Expand Up @@ -392,8 +400,7 @@ private void mergeVertexSerialiser(final Schema thatSchema) {
if (null == getThisSchema().vertexSerialiser) {
getThisSchema().vertexSerialiser = thatSchema.getVertexSerialiser();
} else if (!getThisSchema().vertexSerialiser.getClass().equals(thatSchema.getVertexSerialiser().getClass())) {
throw new SchemaException(UNABLE_TO_MERGE_SCHEMAS_CONFLICT_WITH_VERTEX_SERIALISER_OPTIONS_ARE
+ getThisSchema().vertexSerialiser.getClass().getName() + " and " + thatSchema.getVertexSerialiser().getClass().getName());
throw new VertexSerialiserSchemaException(getThisSchema().vertexSerialiser.getClass().getName(), thatSchema.getVertexSerialiser().getClass().getName());
}
}
}
Expand All @@ -402,8 +409,7 @@ private void mergeVisibility(final Schema thatSchema) {
if (null == getThisSchema().visibilityProperty) {
getThisSchema().visibilityProperty = thatSchema.getVisibilityProperty();
} else if (null != thatSchema.getVisibilityProperty() && !getThisSchema().visibilityProperty.equals(thatSchema.getVisibilityProperty())) {
throw new SchemaException("Unable to merge schemas. Conflict with visibility property, options are: "
+ getThisSchema().visibilityProperty + " and " + thatSchema.getVisibilityProperty());
throw new VisibilityPropertySchemaException(getThisSchema().visibilityProperty, thatSchema.getVisibilityProperty());
}
}

Expand All @@ -418,7 +424,14 @@ private void mergeTypes(final Schema thatSchema) {
if (null == typeDef) {
getThisSchema().types.put(newType, newTypeDef);
} else {
typeDef.merge(newTypeDef);
try {
typeDef.merge(newTypeDef);
} catch (final SchemaException e) {
e.prependToMessage(String.format(FORMAT_ERROR_WITH_THE_SCHEMA_TYPE_NAMED_S_DUE_TO_S, entry.getKey(), ""));
throw e;
} catch (final Exception e) {
throw new SchemaException(String.format(FORMAT_ERROR_WITH_THE_SCHEMA_TYPE_NAMED_S_DUE_TO_S, entry.getKey(), e.getMessage()), e);
}
}
}
}
Expand All @@ -437,9 +450,11 @@ private void mergeElements(final Schema thatSchema) {
mergeEdges(thatSchema);
}

private void validateSharedGroupsAreCompatible(final Schema schema) {
validateSharedGroupsAreCompatible(getThisSchema().getEntities(), schema.getEntities());
validateSharedGroupsAreCompatible(getThisSchema().getEdges(), schema.getEdges());
private void validateSharedGroupsAreCompatible(final Schema thatSchema) {
final String thisVisibilityProperty = getThisSchema().visibilityProperty;
final String thatVisibilityProperty = thatSchema.visibilityProperty;
validateSharedGroupsAreCompatible(getThisSchema().getEntities(), thatSchema.getEntities(), thisVisibilityProperty, thatVisibilityProperty);
validateSharedGroupsAreCompatible(getThisSchema().getEdges(), thatSchema.getEdges(), thisVisibilityProperty, thatVisibilityProperty);
}

@JsonIgnore
Expand Down Expand Up @@ -480,39 +495,43 @@ private Schema getThisSchema() {
return getElementDefs();
}

private void validateSharedGroupsAreCompatible(final Map<String, ? extends SchemaElementDefinition> elements1, final Map<String, ? extends SchemaElementDefinition> elements2) {
final Set<String> sharedGroups = new HashSet<>(elements1.keySet());
sharedGroups.retainAll(elements2.keySet());
private void validateSharedGroupsAreCompatible(final Map<String, ? extends SchemaElementDefinition> thisElements, final Map<String, ? extends SchemaElementDefinition> thatElements, final String thisVisibilityProperty, final String thatVisibilityProperty) {
final Set<String> sharedGroups = new HashSet<>(thisElements.keySet());
sharedGroups.retainAll(thatElements.keySet());

if (!sharedGroups.isEmpty()) {
// Groups are shared. Check they are compatible.
for (final String sharedGroup : sharedGroups) {

// Check if just one group has the properties and groupBy fields set.
final SchemaElementDefinition elementDef1 = elements1.get(sharedGroup);
if ((null == elementDef1.properties || elementDef1.properties.isEmpty())
&& elementDef1.groupBy.isEmpty()) {
final SchemaElementDefinition thisElementDef = thisElements.get(sharedGroup);

if ((null == thisElementDef.properties || thisElementDef.properties.isEmpty())
&& thisElementDef.groupBy.isEmpty()) {
continue;
}
final SchemaElementDefinition elementDef2 = elements2.get(sharedGroup);
if ((null == elementDef2.properties || elementDef2.properties.isEmpty())
&& elementDef2.groupBy.isEmpty()) {
final SchemaElementDefinition thatElementDef = thatElements.get(sharedGroup);
if ((null == thatElementDef.properties || thatElementDef.properties.isEmpty())
&& thatElementDef.groupBy.isEmpty()) {
continue;
}

final Map<String, String> thisProperties = getPropertiesWithoutVisibility(thisVisibilityProperty, thisElementDef);
final Map<String, String> thatProperties = getPropertiesWithoutVisibility(thatVisibilityProperty, thatElementDef);

// Check to see if the properties are the same.
if (Objects.equals(elementDef1.properties, elementDef2.properties)
&& Objects.equals(elementDef1.groupBy, elementDef2.groupBy)) {
if (Objects.equals(thisProperties, thatProperties)
&& Objects.equals(thisElementDef.groupBy, thatElementDef.groupBy)) {
continue;
}

// Check to see if either of the properties are a subset of another properties
if (elementDef2.properties.entrySet().containsAll(elementDef1.properties.entrySet()) ||
elementDef1.properties.entrySet().containsAll(elementDef2.properties.entrySet())) {
if (thatProperties.entrySet().containsAll(thisProperties.entrySet())
|| thisProperties.entrySet().containsAll(thatProperties.entrySet())) {
continue;
}

throw new SchemaException("Element group properties cannot be defined in different schema parts, they must all be defined in a single schema part. "
+ "Please fix this group: " + sharedGroup);
throw new SplitElementGroupDefSchemaException(sharedGroup);
}
}
}
Expand All @@ -532,6 +551,12 @@ private void validateGroupNames() {

getThisSchema().getEntityGroups().forEach(GroupUtil::validateName);
}

private static Map<String, String> getPropertiesWithoutVisibility(final String visibilityProperty, final SchemaElementDefinition elementDef) {
return elementDef.properties.entrySet().stream()
.filter(s -> !Objects.equals(s.getKey(), visibilityProperty))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
}

@JsonPOJOBuilder(buildMethodName = "build", withPrefix = "")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2020 Crown Copyright
* Copyright 2016-2023 Crown Copyright
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -39,6 +39,9 @@
import java.util.function.BinaryOperator;
import java.util.function.Predicate;

import static uk.gov.gchq.gaffer.store.schema.Schema.FORMAT_EXCEPTION;
import static uk.gov.gchq.gaffer.store.schema.Schema.FORMAT_UNABLE_TO_MERGE_SCHEMAS_CONFLICT_WITH_S;

/**
* A {@code TypeDefinition} contains the an object's java class along with how to validate and aggregate the object.
* It is used to deserialise/serialise a {@link Schema} to/from JSON.
Expand All @@ -49,6 +52,13 @@
}, alphabetic = true)
@JsonFilter(JSONSerialiser.FILTER_FIELDS_BY_NAME)
public class TypeDefinition {

public static final String TYPE_CLASS = "type class";
public static final String SCHEMAS_CONFLICT_WITH_TYPE_CLASS = String.format(FORMAT_UNABLE_TO_MERGE_SCHEMAS_CONFLICT_WITH_S, TYPE_CLASS);
public static final String TYPE_SERIALISER = "type serialiser";
public static final String SCHEMAS_CONFLICT_WITH_TYPE_SERIALISER = String.format(FORMAT_UNABLE_TO_MERGE_SCHEMAS_CONFLICT_WITH_S, TYPE_SERIALISER);
public static final String AGGREGATE_FUNCTION = "aggregate function";
public static final String SCHEMAS_CONFLICT_WITH_AGGREGATE_FUNCTION = String.format(FORMAT_UNABLE_TO_MERGE_SCHEMAS_CONFLICT_WITH_S, AGGREGATE_FUNCTION);
private Class<?> clazz;
private Serialiser serialiser;
private List<Predicate> validateFunctions;
Expand Down Expand Up @@ -131,16 +141,14 @@ public void merge(final TypeDefinition type) {
if (null == clazz) {
clazz = type.getClazz();
} else if (null != type.getClazz() && !clazz.equals(type.getClazz())) {
throw new SchemaException("Unable to merge schemas. Conflict with type class, options are: "
+ clazz.getName() + " and " + type.getClazz().getName());
throw new SchemaException(String.format(FORMAT_EXCEPTION, SCHEMAS_CONFLICT_WITH_TYPE_CLASS, clazz.getName(), type.getClazz().getName()));
}

if (null != type.getSerialiser()) {
if (null == getSerialiser()) {
setSerialiser(type.getSerialiser());
} else if (!getSerialiser().getClass().equals(type.getSerialiser().getClass())) {
throw new SchemaException("Unable to merge schemas. Conflict with type (" + clazz + ") serialiser, options are: "
+ getSerialiser().getClass().getName() + " and " + type.getSerialiser().getClass().getName());
throw new SchemaException(String.format(FORMAT_EXCEPTION, SCHEMAS_CONFLICT_WITH_TYPE_SERIALISER, getSerialiser().getClass().getName(), type.getSerialiser().getClass().getName()));
}
}

Expand All @@ -159,8 +167,7 @@ public void merge(final TypeDefinition type) {
if (null == aggregateFunction) {
aggregateFunction = type.getAggregateFunction();
} else if (null != type.getAggregateFunction() && !aggregateFunction.equals(type.getAggregateFunction())) {
throw new SchemaException("Unable to merge schemas. Conflict with type (" + clazz + ") aggregate function, options are: "
+ aggregateFunction + " and " + type.getAggregateFunction());
throw new SchemaException(String.format(FORMAT_EXCEPTION, SCHEMAS_CONFLICT_WITH_AGGREGATE_FUNCTION, aggregateFunction, type.getAggregateFunction()));
}

if (null == description) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2023 Crown Copyright
*
* Licensed 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 uk.gov.gchq.gaffer.store.schema.exception;

import uk.gov.gchq.gaffer.data.elementdefinition.exception.SchemaException;

public class SplitElementGroupDefSchemaException extends SchemaException {

private static final String ELEMENT_GROUP_MUST_ALL_BE_DEFINED_IN_A_SINGLE_SCHEMA = "Element group properties cannot be defined in different schema parts, they must all be defined in a single schema part. Please fix this group: ";

public SplitElementGroupDefSchemaException(final String sharedGroup) {
super(ELEMENT_GROUP_MUST_ALL_BE_DEFINED_IN_A_SINGLE_SCHEMA + sharedGroup);
}

public SplitElementGroupDefSchemaException(final String message, final Throwable e) {
super(message, e);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2023 Crown Copyright
*
* Licensed 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 uk.gov.gchq.gaffer.store.schema.exception;

import uk.gov.gchq.gaffer.data.elementdefinition.exception.SchemaException;
import uk.gov.gchq.gaffer.store.schema.Schema;

public class VertexSerialiserSchemaException extends SchemaException {
public static final String VERTEX_SERIALISER = "vertex serialiser";
private static final String SCHEMAS_CONFLICT_WITH_VERTEX_SERIALISER = String.format(Schema.FORMAT_UNABLE_TO_MERGE_SCHEMAS_CONFLICT_WITH_S, VERTEX_SERIALISER);

public VertexSerialiserSchemaException(final String message, final Throwable e) {
super(message, e);
}

public VertexSerialiserSchemaException(final String thisVertexSerialiser, final String thatVertexSerialiser) {
super(String.format(Schema.FORMAT_EXCEPTION, SCHEMAS_CONFLICT_WITH_VERTEX_SERIALISER, thisVertexSerialiser, thatVertexSerialiser));
}
}
Loading

0 comments on commit df0ec86

Please sign in to comment.