Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package org.neo4j.importer.v1;

import static org.neo4j.importer.v1.ImportSpecificationSettings.DEFAULT_SETTINGS;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.StreamReadFeature;
import com.fasterxml.jackson.databind.JsonNode;
Expand All @@ -28,18 +30,11 @@
import com.networknt.schema.SpecVersion.VersionFlag;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.ServiceLoader.Provider;
import java.util.stream.Collectors;
import org.neo4j.importer.v1.actions.Action;
import org.neo4j.importer.v1.actions.ActionDeserializer;
import org.neo4j.importer.v1.actions.ActionProvider;
import org.neo4j.importer.v1.distribution.Neo4jDistribution;
import org.neo4j.importer.v1.sources.Source;
import org.neo4j.importer.v1.sources.SourceDeserializer;
import org.neo4j.importer.v1.sources.SourceProvider;
import org.neo4j.importer.v1.validation.ActionError;
import org.neo4j.importer.v1.validation.InvalidSpecificationException;
import org.neo4j.importer.v1.validation.Neo4jDistributionValidator;
Expand All @@ -59,6 +54,21 @@ public class ImportSpecificationDeserializer {
private static final JsonSchema SCHEMA = JsonSchemaFactory.getInstance(VersionFlag.V202012)
.getSchema(ImportSpecificationDeserializer.class.getResourceAsStream("/spec.v1.json"));

@Deprecated
public static ImportSpecification deserialize(Reader spec) throws SpecificationException {
return unpack(spec, DEFAULT_SETTINGS);
}

@Deprecated
public static ImportSpecification deserialize(Reader spec, Neo4jDistribution neo4jDistribution)
throws SpecificationException {
return unpack(
spec,
ImportSpecificationSettings.builder()
.withRuntimeValidationEnabledAgainst(neo4jDistribution)
.build());
}

/**
* Returns an instance of {@link ImportSpecification} based on the provided {@link Reader} content.
* The result is guaranteed to be consistent with the specification JSON schema.
Expand All @@ -72,26 +82,26 @@ public class ImportSpecificationDeserializer {
* @return an {@link ImportSpecification}
* @throws SpecificationException if parsing, deserialization or validation fail
*/
public static ImportSpecification deserialize(Reader spec) throws SpecificationException {
return deserialize(spec, Optional.empty());
}

public static ImportSpecification deserialize(Reader spec, Neo4jDistribution neo4jDistribution)
public static ImportSpecification deserialize(Reader spec, ImportSpecificationSettings settings)
throws SpecificationException {

return deserialize(spec, Optional.of(neo4jDistribution));
return unpack(spec, settings);
}

private static ImportSpecification deserialize(
Reader rawSpecification, Optional<Neo4jDistribution> neo4jDistribution) throws SpecificationException {
private static ImportSpecification unpack(Reader rawSpecification, ImportSpecificationSettings settings)
throws SpecificationException {

YAMLMapper mapper = initMapper();
YAMLMapper mapper = initMapper(settings);
JsonNode json = parse(mapper, rawSpecification);
validateSchema(SCHEMA, json);
validateSchema(json);

ImportSpecification specification = deserialize(mapper, json);
validateStatically(specification);
validateRuntime(specification, neo4jDistribution);
validateStatically(specification, settings);
var neo4jDistribution = settings.getNeo4jDistribution();
if (neo4jDistribution
.filter(ImportSpecificationDeserializer::isDistributionSupported)
.isPresent()) {
validateRuntime(specification, neo4jDistribution.get());
}
return specification;
}

Expand All @@ -110,13 +120,13 @@ private static ImportSpecification deserialize(
*/
@Deprecated
public static void validateStatically(ImportSpecification specification) throws SpecificationException {
SpecificationValidators.of(loadValidators()).validate(specification);
validateStatically(specification, DEFAULT_SETTINGS);
}

private static YAMLMapper initMapper() {
private static YAMLMapper initMapper(ImportSpecificationSettings settings) {
var module = new SimpleModule();
module.addDeserializer(Source.class, new SourceDeserializer(loadProviders(SourceProvider.class)));
module.addDeserializer(Action.class, new ActionDeserializer(loadProviders(ActionProvider.class)));
module.addDeserializer(Source.class, new SourceDeserializer(Plugins.loadSourceProviders(settings)));
module.addDeserializer(Action.class, new ActionDeserializer(Plugins.loadActionProviders(settings)));
return YAMLMapper.builder()
.addModule(module)
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
Expand Down Expand Up @@ -152,9 +162,9 @@ private static ImportSpecification deserialize(ObjectMapper mapper, JsonNode jso
}
}

private static void validateSchema(JsonSchema schema, JsonNode json) throws InvalidSpecificationException {
private static void validateSchema(JsonNode json) throws InvalidSpecificationException {
Builder builder = SpecificationValidationResult.builder();
schema.validate(json)
SCHEMA.validate(json)
.forEach(msg -> builder.addError(
msg.getInstanceLocation().toString(),
String.format("SCHM-%s", msg.getCode()),
Expand All @@ -165,27 +175,19 @@ private static void validateSchema(JsonSchema schema, JsonNode json) throws Inva
}
}

private static List<SpecificationValidator> loadValidators() {
return ServiceLoader.load(SpecificationValidator.class).stream()
.map(Provider::get)
.collect(Collectors.toList());
private static void validateStatically(ImportSpecification specification, ImportSpecificationSettings settings)
throws SpecificationException {
var validators = Plugins.loadSpecificationValidators(settings);
SpecificationValidators.of(validators).validate(specification);
}

private static void validateRuntime(ImportSpecification spec, Optional<Neo4jDistribution> neo4jDistribution)
private static void validateRuntime(ImportSpecification spec, Neo4jDistribution neo4jDistribution)
throws SpecificationException {
if (neo4jDistribution
.filter(distribution -> distribution.isVersionLargerThanOrEqual("4.4"))
.isEmpty()) {
return;
}
var runtimeValidator = new Neo4jDistributionValidator(neo4jDistribution.get());
var runtimeValidator = new Neo4jDistributionValidator(neo4jDistribution);
SpecificationValidators.of(runtimeValidator).validate(spec);
}

@SuppressWarnings("unchecked")
private static <T> List<T> loadProviders(Class<?> type) {
return ServiceLoader.load(type).stream()
.map(provider -> (T) provider.get())
.collect(Collectors.toList());
private static boolean isDistributionSupported(Neo4jDistribution distribution) {
return distribution.isVersionLargerThanOrEqual("4.4");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* 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 org.neo4j.importer.v1;

import static java.util.Collections.unmodifiableSet;

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import org.neo4j.importer.v1.actions.Action;
import org.neo4j.importer.v1.actions.ActionProvider;
import org.neo4j.importer.v1.actions.plugin.CypherActionProvider;
import org.neo4j.importer.v1.distribution.Neo4jDistribution;
import org.neo4j.importer.v1.sources.Source;
import org.neo4j.importer.v1.sources.SourceProvider;
import org.neo4j.importer.v1.targets.EntityTargetExtension;
import org.neo4j.importer.v1.targets.EntityTargetExtensionProvider;
import org.neo4j.importer.v1.validation.SpecificationValidator;

public class ImportSpecificationSettings {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: docs


public static final ImportSpecificationSettings DEFAULT_SETTINGS = builder().build();

private final boolean automaticPluginDiscoveryEnabled;
private final Set<Class<? extends SpecificationValidator>> specificationValidators;
private final Set<Class<? extends SourceProvider<? extends Source>>> sourceProviders;
private final Set<Class<? extends ActionProvider<? extends Action>>> actionProviders;
private final Set<Class<? extends EntityTargetExtensionProvider<? extends EntityTargetExtension>>>
entityTargetExtensionProviders;
private final Neo4jDistribution neo4jDistribution;

public ImportSpecificationSettings(
boolean automaticPluginDiscoveryEnabled,
Set<Class<? extends SpecificationValidator>> specificationValidators,
Set<Class<? extends SourceProvider<? extends Source>>> sourceProviders,
Set<Class<? extends ActionProvider<? extends Action>>> actionProviders,
Set<Class<? extends EntityTargetExtensionProvider<? extends EntityTargetExtension>>>
entityTargetExtensionProviders,
Neo4jDistribution neo4jDistribution) {
this.automaticPluginDiscoveryEnabled = automaticPluginDiscoveryEnabled;
this.specificationValidators = unmodifiableSet(specificationValidators);
this.sourceProviders = unmodifiableSet(sourceProviders);
this.actionProviders = unmodifiableSet(actionProviders);
this.entityTargetExtensionProviders = unmodifiableSet(entityTargetExtensionProviders);
this.neo4jDistribution = neo4jDistribution;
}

public static Builder builder() {
return new Builder();
}

public boolean isAutomaticPluginDiscoveryEnabled() {
return automaticPluginDiscoveryEnabled;
}

public Set<Class<? extends SpecificationValidator>> getSpecificationValidators() {
return specificationValidators;
}

public Set<Class<? extends SourceProvider<? extends Source>>> getSourceProviders() {
return sourceProviders;
}

public Set<Class<? extends ActionProvider<? extends Action>>> getActionProviders() {
return actionProviders;
}

public Set<Class<? extends EntityTargetExtensionProvider<? extends EntityTargetExtension>>>
Copy link
Contributor Author

@fbiville fbiville Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: I need to add the companion method for this in Plugins, and rely on that method somehow in

@SuppressWarnings("unchecked")
private static final List<EntityTargetExtensionProvider<? extends EntityTargetExtension>> EXTENSION_PROVIDERS =
ServiceLoader.load(EntityTargetExtensionProvider.class).stream()
.map(provider -> (EntityTargetExtensionProvider<? extends EntityTarget>) provider.get())
.collect(Collectors.toList());

not sure how to do the latter...

getEntityTargetExtensionProviders() {
return entityTargetExtensionProviders;
}

public Optional<Neo4jDistribution> getNeo4jDistribution() {
return Optional.ofNullable(neo4jDistribution);
}

@Override
public String toString() {
return "ImportSpecificationConfig{" + "enableAutomaticPluginDiscovery="
+ automaticPluginDiscoveryEnabled + ", sourceProviders="
+ sourceProviders + ", actionProviders="
+ actionProviders + ", entityTargetExtensionProviders="
+ entityTargetExtensionProviders + ", neo4jDistribution="
+ neo4jDistribution + '}';
}

public static class Builder {
private boolean automaticPluginDiscoveryEnabled = true;
private final Set<Class<? extends SpecificationValidator>> specificationValidators =
Plugins.loadBuiltInSpecificationValidators();
private final Set<Class<? extends SourceProvider<? extends Source>>> sourceProviders = new HashSet<>();
private final Set<Class<? extends ActionProvider<? extends Action>>> actionProviders =
new HashSet<>(Set.of(CypherActionProvider.class));
private final Set<Class<? extends EntityTargetExtensionProvider<? extends EntityTargetExtension>>>
entityTargetExtensionProviders = new HashSet<>();
private Neo4jDistribution neo4jDistribution;

private Builder() {}

public Builder withAutomaticPluginDiscoveryDisabled() {
this.automaticPluginDiscoveryEnabled = false;
return this;
}

public Builder withAutomaticPluginDiscoveryEnabled() {
this.automaticPluginDiscoveryEnabled = true;
return this;
}

@SafeVarargs
public final Builder withSpecificationValidators(
Class<SpecificationValidator> specificationValidator, Class<SpecificationValidator>... otherProviders) {
return withSpecificationValidators(concat(specificationValidator, otherProviders));
}

public Builder withSpecificationValidators(
Set<Class<? extends SpecificationValidator>> specificationValidators) {
this.specificationValidators.addAll(specificationValidators);
return this;
}

@SafeVarargs
public final Builder withSourceProviders(
Class<SourceProvider<? extends Source>> sourceProvider,
Class<SourceProvider<? extends Source>>... otherProviders) {
return withSourceProviders(concat(sourceProvider, otherProviders));
}

public Builder withSourceProviders(Set<Class<? extends SourceProvider<? extends Source>>> sourceProviders) {
this.sourceProviders.addAll(sourceProviders);
return this;
}

@SafeVarargs
public final Builder withActionProviders(
Class<? extends ActionProvider<? extends Action>> actionProvider,
Class<? extends ActionProvider<? extends Action>>... otherProviders) {
return withActionProviders(concat(actionProvider, otherProviders));
}

public Builder withActionProviders(Set<Class<? extends ActionProvider<? extends Action>>> actionProviders) {
this.actionProviders.addAll(actionProviders);
return this;
}

@SafeVarargs
public final Builder withEntityTargetExtensionProviders(
Class<? extends EntityTargetExtensionProvider<? extends EntityTargetExtension>>
entityTargetExtensionProvider,
Class<? extends EntityTargetExtensionProvider<? extends EntityTargetExtension>>... otherProviders) {
return withEntityTargetExtensionProviders(concat(entityTargetExtensionProvider, otherProviders));
}

public Builder withEntityTargetExtensionProviders(
Set<Class<? extends EntityTargetExtensionProvider<? extends EntityTargetExtension>>>
entityTargetExtensionProviders) {
this.entityTargetExtensionProviders.addAll(entityTargetExtensionProviders);
return this;
}

public Builder withRuntimeValidationEnabledAgainst(Neo4jDistribution neo4jDistribution) {
this.neo4jDistribution = neo4jDistribution;
return this;
}

public ImportSpecificationSettings build() {
return new ImportSpecificationSettings(
automaticPluginDiscoveryEnabled,
specificationValidators,
sourceProviders,
actionProviders,
entityTargetExtensionProviders,
neo4jDistribution);
}

private static <T> Set<T> concat(T sourceProvider, T[] otherProviders) {
var providers = new LinkedHashSet<T>(1 + otherProviders.length);
providers.add(sourceProvider);
providers.addAll(Arrays.asList(otherProviders));
return providers;
}
}
}
Loading