Skip to content

Commit

Permalink
Placeholder pipeline: Replace Option<String> by dedicated PipelineEle…
Browse files Browse the repository at this point in the history
…ment.

Reason: To add another pipeline function fn:delete that should delete the entire
string containing the pipeline, which is not evaluated yet.

Signed-off-by: Yufei Cai <yufei.cai@bosch-si.com>
  • Loading branch information
yufei-cai committed Oct 30, 2019
1 parent 42f7ff4 commit f174579
Show file tree
Hide file tree
Showing 32 changed files with 703 additions and 246 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public interface ExpressionResolver {
* @throws PlaceholderFunctionTooComplexException thrown if the {@code expressionTemplate} contains a placeholder
* function chain which is too complex (e.g. too much chained function calls)
*/
// TODO change signature
String resolve(String expressionTemplate, boolean allowUnresolved);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
*/
package org.eclipse.ditto.model.placeholders;

import java.util.Optional;

/**
* Defines a function expression used in a Pipeline after the "input" stage of a resolved {@link Placeholder}, e.g.
* function expressions are expressions like {@code fn:starts-with(':')} or {@code fn:default('fallback')}. Used in a
Expand All @@ -30,11 +28,11 @@ interface FunctionExpression extends Expression {
* Executes the Stage by passing in a value and returning a resolved result.
*
* @param expression the expression string of this stage including prefix, e.g.: {@code fn:substring-before(':')}.
* @param resolvedInputValue the resolved input value (e.g. via {@link Placeholder} to process.
* @param resolvedInputValue the resolved input value (e.g. via {@link org.eclipse.ditto.model.placeholders.Placeholder} to process.
* @param expressionResolver the expressionResolver to use in order to resolve placeholders occurring in the
* pipeline expression.
* @return processed output value, or an empty optional if this stage resolved to an empty Optional.
*/
Optional<String> resolve(String expression, Optional<String> resolvedInputValue,
PipelineElement resolve(String expression, PipelineElement resolvedInputValue,
ExpressionResolver expressionResolver);
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,21 @@ private Function<String, Optional<String>> makePlaceholderReplacerFunction(

final Optional<String> placeholderWithoutPrefix =
resolvePlaceholderWithoutPrefixIfSupported(placeholderResolver, placeholderTemplate);
return placeholderWithoutPrefix
.map(p -> resolvePlaceholder(placeholderResolver, p))
return placeholderWithoutPrefix.map(p -> resolvePlaceholder(placeholderResolver, p))
.flatMap(pipelineInput -> {
if (Optional.of(placeholderReplacementInValidation).equals(pipelineInput)) {
if (pipelineInput.filter(placeholderReplacementInValidation::equals).isPresent()) {
pipeline.validate();
// let the input pass if validation succeeded:
return pipelineInput;
}
return pipeline.execute(pipelineInput, this);
final PipelineElement element = pipelineInput.map(PipelineElement::resolved)
.orElse(PipelineElement.unresolved());
return pipeline.execute(element, this)
.accept(PipelineElement.<Optional<String>>newVisitorBuilder()
.deleted(Optional.empty()) // TODO: distinguish
.unresolved(Optional.empty())
.resolved(Optional::of)
.build());
});

};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.annotation.concurrent.Immutable;
Expand All @@ -32,11 +31,12 @@ final class ImmutableFunctionExpression implements FunctionExpression {
static final ImmutableFunctionExpression INSTANCE = new ImmutableFunctionExpression();

private static final List<PipelineFunction> SUPPORTED = Collections.unmodifiableList(Arrays.asList(
new PipelineFunctionDefault(), // fn:default('fallback value')
new PipelineFunctionDefault(), // fn:default('fallback value')
new PipelineFunctionSubstringBefore(), // fn:substring-before(':')
new PipelineFunctionSubstringAfter(), // fn:substring-after(':')
new PipelineFunctionLower(), // fn:lower()
new PipelineFunctionUpper() // fn:upper()
new PipelineFunctionSubstringAfter(), // fn:substring-after(':')
new PipelineFunctionLower(), // fn:lower()
new PipelineFunctionUpper(), // fn:upper()
new PipelineFunctionDelete() // fn:delete()
));

@Override
Expand All @@ -63,7 +63,7 @@ public boolean supports(final String expressionName) {
}

@Override
public Optional<String> resolve(final String expression, final Optional<String> resolvedInputValue,
public PipelineElement resolve(final String expression, final PipelineElement resolvedInputValue,
final ExpressionResolver expressionResolver) {

if (!supports(expression.replaceFirst(getPrefix() + ":", ""))) {
Expand All @@ -77,7 +77,7 @@ public Optional<String> resolve(final String expression, final Optional<String>
expressionResolver)
)
.findFirst()
.flatMap(o -> o);
.orElse(PipelineElement.unresolved());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import javax.annotation.concurrent.Immutable;

Expand All @@ -35,13 +34,13 @@ final class ImmutablePipeline implements Pipeline {
}

@Override
public Optional<String> execute(final Optional<String> pipelineInput, final ExpressionResolver expressionResolver) {
public PipelineElement execute(final PipelineElement pipelineInput, final ExpressionResolver expressionResolver) {

Optional<String> stageValue = pipelineInput;
for (final String expression : stageExpressions) {
stageValue = functionExpression.resolve(expression, stageValue, expressionResolver);
}
return stageValue;
return stageExpressions.stream().reduce(
pipelineInput,
(element, expression) -> functionExpression.resolve(expression, element, expressionResolver),
ImmutablePipeline::combineElements
);
}

@Override
Expand Down Expand Up @@ -82,4 +81,18 @@ public String toString() {
", stageExpressions=" + stageExpressions +
"]";
}

private static PipelineElement combineElements(final PipelineElement self, final PipelineElement other) {
return self.onDeleted(() -> self)
.onUnresolved(() -> other)
.onResolved(s -> other.onDeleted(() -> other)
.onUnresolved(() -> self)
.onResolved(t -> {
// should not happen - stream of stage expressions is not parallel
throw new IllegalArgumentException(
String.format("Conflict: combining 2 resolved elements <%s> and <%s>", s, t)
);
})
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright (c) 2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.model.placeholders;

import static org.eclipse.ditto.model.base.common.ConditionChecker.checkNotNull;

import java.util.function.Function;

import javax.annotation.Nullable;

/**
* Package-private implementation of {@code PipelineElementVisitor}.
*
* @param <T> type of visitor result.
*/
final class ImmutablePipelineElementVisitor<T> implements PipelineElementVisitor<T> {

private final Function<String, T> onResolution;
private final T onIrresolution;
private final T onDeletion;

private ImmutablePipelineElementVisitor(
final Function<String, T> onResolution,
final T onIrresolution,
final T onDeletion) {
this.onResolution = onResolution;
this.onIrresolution = onIrresolution;
this.onDeletion = onDeletion;
}

static <T> PipelineElementVisitor.Builder<T> newBuilder() {
return new Builder<>();
}

@Override
public T resolved(final String resolved) {
return onResolution.apply(resolved);
}

@Override
public T unresolved() {
return onIrresolution;
}

@Override
public T deleted() {
return onDeletion;
}

private static final class Builder<T> implements PipelineElementVisitor.Builder<T> {

@Nullable private Function<String, T> onResolution;
@Nullable private T onIrresolution;
@Nullable private T onDeletion;

private Builder() {}

@Override
public PipelineElementVisitor<T> build() {
return new ImmutablePipelineElementVisitor<>(
checkNotNull(onResolution, "onResolution"),
checkNotNull(onIrresolution, "onIrresolution"),
checkNotNull(onDeletion, "onDeletion"));
}

@Override
public PipelineElementVisitor.Builder<T> resolved(final Function<String, T> onResolution) {
this.onResolution = onResolution;
return this;
}

@Override
public PipelineElementVisitor.Builder<T> unresolved(final T onIrresolution) {
this.onIrresolution = onIrresolution;
return this;
}

@Override
public PipelineElementVisitor.Builder<T> deleted(final T onDeletion) {
this.onDeletion = onDeletion;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
*/
package org.eclipse.ditto.model.placeholders;

import java.util.Optional;

/**
* A Pipeline is able to execute its {@code stageExpressions} starting with a {@code pipelineInput} derived from a
* {@link Placeholder}.
Expand All @@ -23,12 +21,12 @@ interface Pipeline {
/**
* Executes the Pipeline function expressions by first evaluating the placeholder variable by name.
*
* @param pipelineInput the input into the pipe, usually an already resolved {@link Placeholder} - may also be an
* @param pipelineInput the input into the pipe, usually an already resolved {@link org.eclipse.ditto.model.placeholders.Placeholder} - may also be an
* empty optional as there are pipeline stages which can make use of a default fallback value.
* @param expressionResolver the resolver from which placeholders are resolved.
* @return the result of the Pipeline execution after all stages were handled.
*/
Optional<String> execute(Optional<String> pipelineInput, ExpressionResolver expressionResolver);
PipelineElement execute(PipelineElement pipelineInput, ExpressionResolver expressionResolver);

/**
* Validates the instantiated Pipeline and checks whether all configured {@code stageExpressions} are supported.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.model.placeholders;

import java.util.function.Function;
import java.util.function.Supplier;

/**
* An element going through some pipeline of functions.
*/
public interface PipelineElement extends Iterable<String> {

/**
* Advance a resolved value to the next stage. Other elements are unchanged.
*
* @param stringProcessor What to do for resolved values.
* @return the pipeline element at the next pipeline stage.
*/
PipelineElement onResolved(Function<String, PipelineElement> stringProcessor);

/**
* Advance an unresolved value to the next stage. Other elements are unchanged.
*
* @param nextPipelineElement supplier of the next pipeline element.
* @return the pipeline element at the next pipeline stage.
*/
PipelineElement onUnresolved(Supplier<PipelineElement> nextPipelineElement);

/**
* Advance a deleted value to the next stage. Other elements are unchanged.
*
* @param nextPipelineElement supplier of the next pipeline element.
* @return the pipeline element at the next pipeline stage.
*/
PipelineElement onDeleted(Supplier<PipelineElement> nextPipelineElement);

/**
* Evaluate this pipeline element by a visitor.
*
* @param visitor the visitor.
* @param <T> the type of results.
* @return the evaluation result.
*/
<T> T accept(PipelineElementVisitor<T> visitor);

/**
* Convert a resolved value into another resolved value and leave other elements untouched.
*
* @param mapper what to do about the resolved value.
* @return the mapped resolved value.
*/
default PipelineElement map(Function<String, String> mapper) {
return onResolved(mapper.andThen(PipelineElement::resolved));
}

/**
* Create a builder of a visitor to evaluate pipeline elements.
*
* @param <T> the type of results.
* @return the visitor builder.
*/
static <T> PipelineElementVisitor.Builder<T> newVisitorBuilder() {
return ImmutablePipelineElementVisitor.newBuilder();
}

/**
* Creat a pipeline element containing a resolved value.
*
* @param value the resolved value.
* @return the pipeline element.
*/
static PipelineElement resolved(final String value) {
return PipelineElementResolved.of(value);
}

/**
* Get the unique pipeline element signifying deletion of the whole string containing the pipeline.
*
* @return the deleted element.
*/
static PipelineElement deleted() {
return PipelineElementDeleted.INSTANCE;
}

/**
* Get the unique pipeline element signifying failed resolution.
*
* @return the unresolved element.
*/
static PipelineElement unresolved() {
return PipelineElementUnresolved.INSTANCE;
}
}
Loading

0 comments on commit f174579

Please sign in to comment.