/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 io.dstream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.dstream.DStreamInvocationChain.DStreamInvocation;
import io.dstream.utils.Assert;
import io.dstream.utils.PropertiesHelper;
import io.dstream.utils.ReflectionUtils;
/**
*
* @param <T>
* @param <R>
*/
final class DStreamExecutionGraphsBuilder<T,R> {
private final Logger logger = Logger.getLogger(this.getClass().getName());
private final R targetStream;
private final DStreamInvocationChain invocationPipeline;
private final Set<String> streamOperationNames;
private final Class<?> currentStreamType;
/**
*
* @param sourceElementType
* @param sourceIdentifier
* @param streamType
* @return
*/
static <T,R> R as(Class<T> sourceElementType, String sourceIdentifier, Class<R> streamType) {
StreamNameMonitor.add(sourceIdentifier);
@SuppressWarnings("unchecked")
DStreamExecutionGraphsBuilder<T,R> builder = new DStreamExecutionGraphsBuilder<T,R>(sourceElementType, sourceIdentifier, streamType);
return builder.targetStream;
}
/**
*
*/
@SuppressWarnings("unchecked")
private DStreamExecutionGraphsBuilder(Class<?> sourceElementType, String sourceIdentifier, Class<R>... streamType) {
this.currentStreamType = streamType[0];
this.targetStream = this.generateStreamProxy(streamType);
this.invocationPipeline = new DStreamInvocationChain(sourceElementType, sourceIdentifier, streamType[0]);
this.streamOperationNames = ReflectionUtils.findAllVisibleMethodOnInterface(streamType[0]);
this.streamOperationNames.remove("ofType");
this.streamOperationNames.remove("executeAs");
this.streamOperationNames.remove("getName");
}
/**
*
*/
@Override
public String toString(){
return this.invocationPipeline.getSourceIdentifier() + ":" +
this.invocationPipeline.getInvocations().stream().map(s -> s.getMethod().getName()).collect(Collectors.toList());
}
/**
*
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private R cloneTargetDistributable(Method method, Object[] arguments){
Ops operation = Ops.valueOf(method.getName());
if (operation.equals(Ops.join) || operation.equals(Ops.union) || operation.equals(Ops.unionAll)){
StreamInvocationChainSupplier s = (StreamInvocationChainSupplier) arguments[0];
arguments = new Object[]{s.get()};
}
DStreamExecutionGraphsBuilder clonedDistributable = new DStreamExecutionGraphsBuilder(this.invocationPipeline.getSourceElementType(), this.invocationPipeline.getSourceIdentifier(),
method.getReturnType().isInterface() ? method.getReturnType() : this.currentStreamType);
clonedDistributable.invocationPipeline.addAllInvocations(this.invocationPipeline.getInvocations());
if (operation.equals(Ops.on)){
clonedDistributable.invocationPipeline.getLastInvocation().setSupplementaryOperation(arguments[0]);
}
else {
clonedDistributable.invocationPipeline.addInvocation(new DStreamInvocation(method, arguments));
}
return (R) clonedDistributable.targetStream;
}
/**
*
*/
@SuppressWarnings("unchecked")
private R generateStreamProxy(Class<?>... proxyTypes){
List<Class<?>> interfaces = Stream.of(proxyTypes).collect(Collectors.toList());
interfaces.add(StreamInvocationChainSupplier.class);
return (R) Proxy.newProxyInstance(this.getClass().getClassLoader(), interfaces.toArray(new Class[]{}), new StreamInvocationHandler());
}
/**
*
*/
private Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String operationName = method.getName();
Object result;
if (this.streamOperationNames.contains(operationName)){
logger.info("Invoking OPERATION: " + operationName);
result = this.cloneTargetDistributable(method, args == null ? new Object[]{} : args);
}
else if (operationName.equals("getName")){
result = this.invocationPipeline.getSourceIdentifier();
}
else if (operationName.equals("get")){
result = this.invocationPipeline;
}
else if (operationName.equals("executeAs")){
StreamNameMonitor.reset();
String executionName = (String) args[0];
Assert.notEmpty(executionName, "'executionName' must not be null or empty");
Properties executionConfig = PropertiesHelper.loadProperties(executionName + ".cfg");
String executionDelegateClassName = executionConfig.getProperty(DStreamConstants.DELEGATE);
Assert.notEmpty(executionDelegateClassName, "Execution delegate property is not provided in '" + executionName +
".cfg' (e.g., dstream.delegate=foo.bar.SomePipelineDelegate)");
logger.info("Delegating execution to: " + executionDelegateClassName);
DStreamExecutionGraphBuilder builder = new DStreamExecutionGraphBuilder(this.invocationPipeline, executionConfig);
DStreamExecutionGraph operations = builder.build();
DStreamExecutionDelegate executionDelegate = (DStreamExecutionDelegate) ReflectionUtils
.newDefaultInstance(Class.forName(executionDelegateClassName, true, Thread.currentThread().getContextClassLoader()));
result = executionDelegate.execute(executionName, executionConfig, operations);
}
else {
result = method.invoke(this, args);
}
return result;
}
/**
*/
private static interface StreamInvocationChainSupplier {
DStreamInvocationChain get();
}
/**
*
*/
private class StreamInvocationHandler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return DStreamExecutionGraphsBuilder.this.invoke(proxy, method, args);
}
}
/**
*
*/
static class StreamNameMonitor {
private final static ThreadLocal<Set<String>> tl = ThreadLocal.withInitial(() -> new HashSet<String>());
static void add(String name){
Assert.isFalse(tl.get().contains(name), "Stream with the name '" + name + "' already exists");
tl.get().add(name);
}
static void reset(){
tl.get().clear();
}
}
}