/*
* 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 gobblin.runtime.cli;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Map;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import com.google.common.collect.Maps;
import gobblin.runtime.api.JobTemplate;
import gobblin.runtime.embedded.EmbeddedGobblin;
/**
* A helper class for automatically inferring {@link Option}s from the constructor and public methods in a class.
*
* For method inference, see {@link PublicMethodsGobblinCliFactory}.
*
* {@link Option}s are inferred from the constructor as follows:
* 1. The helper will search for exactly one constructor with only String arguments and which is annotated with
* {@link EmbeddedGobblinCliSupport}.
* 2. For each parameter of the constructor, the helper will create a required {@link Option}.
*
* For an example usage see {@link EmbeddedGobblin.CliFactory}.
*/
public abstract class ConstructorAndPublicMethodsGobblinCliFactory extends PublicMethodsGobblinCliFactory {
private final Constructor<? extends EmbeddedGobblin> constructor;
private final Map<String, Integer> constructoArgumentsMap;
private final Options options;
public ConstructorAndPublicMethodsGobblinCliFactory(Class<? extends EmbeddedGobblin> klazz) {
super(klazz);
this.constructoArgumentsMap = Maps.newHashMap();
this.options = super.getOptions();
this.constructor = inferConstructorOptions(this.options);
}
@Override
public EmbeddedGobblin constructEmbeddedGobblin(CommandLine cli)
throws JobTemplate.TemplateException, IOException {
return buildInstance(cli);
}
@Override
public Options getOptions() {
return this.options;
}
/**
* Builds an instance of {@link EmbeddedGobblin} using the selected constructor getting the constructor
* parameters from the {@link CommandLine}.
*
* Note: this method will also automatically call {@link #applyCommandLineOptions(CommandLine, EmbeddedGobblin)} on
* the constructed {@link EmbeddedGobblin}.
*/
public EmbeddedGobblin buildInstance(CommandLine cli) {
String[] constructorArgs = new String[this.constructor.getParameterTypes().length];
for (Option option : cli.getOptions()) {
if (this.constructoArgumentsMap.containsKey(option.getOpt())) {
int idx = this.constructoArgumentsMap.get(option.getOpt());
constructorArgs[idx] = option.getValue();
}
}
EmbeddedGobblin embeddedGobblin;
try {
embeddedGobblin = this.constructor.newInstance((Object[]) constructorArgs);
return embeddedGobblin;
} catch (IllegalAccessException | InvocationTargetException | InstantiationException exc) {
throw new RuntimeException("Could not instantiate " + this.klazz.getName(), exc);
}
}
private Constructor<? extends EmbeddedGobblin> inferConstructorOptions(Options otherOptions) {
Constructor<? extends EmbeddedGobblin> selectedConstructor = null;
for (Constructor<?> constructor : this.klazz.getConstructors()) {
if (canUseConstructor(constructor)) {
if (selectedConstructor == null) {
selectedConstructor = (Constructor<? extends EmbeddedGobblin>) constructor;
} else {
throw new RuntimeException("Multiple usable constructors for " + this.klazz.getName());
}
}
}
if (selectedConstructor == null) {
throw new RuntimeException("There is no usable constructor for " + this.klazz.getName());
}
int constructorIdx = 0;
for (String argument : selectedConstructor.getAnnotation(EmbeddedGobblinCliSupport.class).argumentNames()) {
Option option = Option.builder(argument).required().hasArg().build();
otherOptions.addOption(option);
constructoArgumentsMap.put(option.getOpt(), constructorIdx);
constructorIdx++;
}
return selectedConstructor;
}
private boolean canUseConstructor(Constructor<?> constructor) {
if (!Modifier.isPublic(constructor.getModifiers())) {
return false;
}
if (!constructor.isAnnotationPresent(EmbeddedGobblinCliSupport.class)) {
return false;
}
for (Class<?> param : constructor.getParameterTypes()) {
if (param != String.class) {
return false;
}
}
return constructor.getParameterTypes().length ==
constructor.getAnnotation(EmbeddedGobblinCliSupport.class).argumentNames().length;
}
}