Skip to content
Open
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
31 changes: 26 additions & 5 deletions core/src/main/java/org/neo4j/importer/v1/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
import java.util.Optional;
import java.util.stream.Stream;

/**
* {@link Configuration} is a simple, type-safe wrapper class over arbitrary top-level {@link ImportSpecification}
* settings.
*/
public class Configuration implements Serializable {

private final Map<String, Object> settings;
Expand All @@ -32,21 +36,25 @@ public Configuration(Map<String, Object> settings) {
this.settings = settings != null ? settings : Collections.emptyMap();
}

/**
* Returns the setting matched by its primary name (or its alternate names), if it conforms to the provided type
* @param type expected value type of the setting
* @param name primary setting name
* @param alternativeNames alternative setting names, if any
* @return the setting value if it matches the provided parameters in an optional wrapper, or an empty optional
* @param <T> expected type of the setting value
*/
public <T> Optional<T> get(Class<T> type, String name, String... alternativeNames) {
if (settings.isEmpty()) {
return Optional.empty();
}
return Stream.concat(Stream.of(name), Arrays.stream(alternativeNames))
.map(key -> getElement(type, key))
.map(key -> get(type, key))
.dropWhile(Optional::isEmpty)
.flatMap(Optional::stream)
.findFirst();
}

private <T> Optional<T> getElement(Class<T> type, String name) {
return settings.containsKey(name) ? Optional.of(type.cast(settings.get(name))) : Optional.empty();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -64,4 +72,17 @@ public int hashCode() {
public String toString() {
return "Configuration{" + "settings=" + settings + '}';
}

private <T> Optional<T> get(Class<T> type, String name) {
return settings.containsKey(name) ? Optional.ofNullable(safeCast(type, name)) : Optional.empty();
}

private <T> T safeCast(Class<T> type, String name) {
var rawValue = settings.get(name);
try {
return type.cast(rawValue);
} catch (ClassCastException e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,35 @@
import org.neo4j.importer.v1.sources.Source;
import org.neo4j.importer.v1.targets.Targets;

/**
* {@link ImportSpecification} is the direct representation of the import-spec configuration YAML/JSON files.<br>
* {@link ImportSpecification} is made of 3 mandatory attributes:
* <ul>
* <li>version</li>
* <li>a list of {@link Source}</li>
* <li>targets, which is a mix of:
* <ul>
* <li>{@link org.neo4j.importer.v1.targets.NodeTarget}</li>
* <li>{@link org.neo4j.importer.v1.targets.RelationshipTarget}</li>
* <li>{@link org.neo4j.importer.v1.targets.CustomQueryTarget}</li>
* </ul>
* </li></ul>
* {@link ImportSpecification} can also include:
* <ul>
* <li>top-level settings, wrapped into {@link Configuration}</li>
* <li>one-off {@link Action} scripts</li>
* </ul><br>
* Note: It is <strong>strongly</strong> discouraged to instantiate this class directly.
* Please call one of the deserialization APIs of {@link ImportSpecificationDeserializer} instead.
* The deserializer automatically loads all built-in and external
* {@link org.neo4j.importer.v1.validation.SpecificationValidator} implementation and runs them against this.<br>
* The direct use of {@link ImportSpecification} is most suited when targeting import tools that fully manage import
* task ordering such as neo4j-admin (neo4j-admin handles the order between schema initialization, node import and
* relationship import itself).<br>
* In other cases, {@link org.neo4j.importer.v1.pipeline.ImportPipeline} is a better choice.<br>
* You can construct an {@link org.neo4j.importer.v1.pipeline.ImportPipeline} instance from an instance of
* {@link ImportSpecification} with {@link org.neo4j.importer.v1.pipeline.ImportPipeline#of(ImportSpecification)}.
*/
public class ImportSpecification implements Serializable {

private final String version;
Expand Down Expand Up @@ -54,22 +83,42 @@ public ImportSpecification(
this.actions = actions;
}

/**
* Returns the {@link ImportSpecification} version
* @return the version
*/
public String getVersion() {
return version;
}

/**
* Returns the {@link ImportSpecification} top-level settings
* @return the top-level settings
*/
public Configuration getConfiguration() {
return configuration;
}

/**
* Returns the {@link ImportSpecification} data sources
* @return the data sources
*/
public List<Source> getSources() {
return sources;
}

/**
* Returns the {@link ImportSpecification} node, relationship and custom query targets, wrapped into {@link Targets}
* @return the targets
*/
public Targets getTargets() {
return targets;
}

/**
* Returns the {@link ImportSpecification} actions, active or not
* @return the actions
*/
public List<Action> getActions() {
return actions != null ? actions : Collections.emptyList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,26 @@ public class ImportSpecificationDeserializer {
.getSchema(ImportSpecificationDeserializer.class.getResourceAsStream("/spec.v1.json"));

/**
* 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.
* Returns an instance of {@link ImportSpecification} based on the provided {@link Reader} content.<br>
* The result is guaranteed to be consistent with the specification JSON schema.<br>
* <br>
* If implementations of the {@link SpecificationValidator} SPI are provided, they will also run against the
* {@link ImportSpecification} instance before the latter is returned.
* <br>
* If the parsing, deserialization or validation (standard or via SPI implementations) fail, a {@link SpecificationException}
* is going to be thrown.
*
* If implementations of the {@link SpecificationValidator} Service Provider Interface are provided, they will also
* run against the {@link ImportSpecification} instance before the latter is returned.<br>
* If the parsing, deserialization or validation (standard or via SPI implementations) fail, a
* {@link SpecificationException} is thrown.
* @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());
}

/**
* Same as {@link ImportSpecificationDeserializer#deserialize(Reader)}, except it checks extra validation rules
* against the provided {@link Neo4jDistribution} value.
* @return an {@link ImportSpecification}
* @throws SpecificationException if parsing, deserialization or validation fail
*/
public static ImportSpecification deserialize(Reader spec, Neo4jDistribution neo4jDistribution)
throws SpecificationException {

Expand Down
16 changes: 16 additions & 0 deletions core/src/main/java/org/neo4j/importer/v1/actions/Action.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,26 @@

import java.io.Serializable;

/**
* {@link Action} define one-off execution scripts.<br>
* The {@link Action} name must be unique within an instance of {@link org.neo4j.importer.v1.ImportSpecification}.<br>
* The {@link Action} stage (see {@link Action#getStage()} determines when an action must be executed.<br>
* Only {@link org.neo4j.importer.v1.actions.plugin.CypherAction} is defined by default.<br>
* Other {@link ActionProvider} implementation may be registered through Java's standard
* Service Provider Interface mechanism.
*/
public interface Action extends Serializable {

boolean DEFAULT_ACTIVE = true;

/**
* Type of the action (example: "jdbc", "http", ...)<br>
* The type name must be unique in a case-insensitive (per {@link java.util.Locale#ROOT} casing rules).<br>
* Having multiple actions loaded with the same type is invalid and will lead to an exception being raised, aborting
* the import early.<br>
* Note: it is recommended that the type returned here be the same as the one this source's {@link ActionProvider}
* supports, or a subset thereof.
*/
String getType();

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,24 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.function.Function;

/**
* {@link ActionProvider} is a Service Provider Interface, allowing users to define their own action.
* @param <T> the concrete type of the {@link Action} subclass
*/
public interface ActionProvider<T extends Action> extends Function<ObjectNode, T> {

/**
* User-facing type of the concrete supported action, it must match the result of {@link Action#getType()}
* @return the action type
*/
String supportedType();

/**
* Deserialization endpoint.<br>
* Note: implement deserialization leniently, validate strictly (define
* {@link org.neo4j.importer.v1.validation.SpecificationValidator} for your own sources)
* @return the deserialized action
*/
@Override
default T apply(ObjectNode node) {
return provide(node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,45 @@
*/
package org.neo4j.importer.v1.actions;

/**
* {@link ActionStage} defines when the enclosing {@link Action} must run.<br>
* {@link org.neo4j.importer.v1.pipeline.ImportPipeline} translate this into concrete dependencies.
*/
public enum ActionStage {
/**
* The enclosing {@link Action} must run before anything else (actively-used sources, active targets)
*/
START,
/**
* The enclosing {@link Action} must run after all actively-used {@link org.neo4j.importer.v1.sources.Source}s
*/
POST_SOURCES,
/**
* The enclosing {@link Action} must run before all active {@link org.neo4j.importer.v1.targets.NodeTarget}s
*/
PRE_NODES,
/**
* The enclosing {@link Action} must run after all active {@link org.neo4j.importer.v1.targets.NodeTarget}s
*/
POST_NODES,
/**
* The enclosing {@link Action} must run before all active {@link org.neo4j.importer.v1.targets.RelationshipTarget}s
*/
PRE_RELATIONSHIPS,
/**
* The enclosing {@link Action} must run after all active {@link org.neo4j.importer.v1.targets.RelationshipTarget}s
*/
POST_RELATIONSHIPS,
/**
* The enclosing {@link Action} must run before all active {@link org.neo4j.importer.v1.targets.CustomQueryTarget}s
*/
PRE_QUERIES,
/**
* The enclosing {@link Action} must run after all active {@link org.neo4j.importer.v1.targets.CustomQueryTarget}s
*/
POST_QUERIES,
/**
* The enclosing {@link Action} must run after everything else (actively-used sources, active targets)
*/
END
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
import org.neo4j.importer.v1.actions.Action;
import org.neo4j.importer.v1.actions.ActionStage;

/**
* {@link CypherAction} defines a one-off Cypher script to execute.<br>
* It defines a query (see {@link CypherAction#getQuery()} and an execution mode {@link CypherExecutionMode} (default:
* {@link CypherExecutionMode#TRANSACTION}).
*/
public class CypherAction implements Action {
static final CypherExecutionMode DEFAULT_CYPHER_EXECUTION_MODE = CypherExecutionMode.TRANSACTION;

Expand Down
Loading