/*
* Copyright (c) 2009-2015
* IT-Consulting Stephan Schloepke (http://www.schloepke.de/)
* klemm software consulting Mirko Klemm (http://www.klemm-scs.com/)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jbasics.command;
import org.jbasics.checker.ContractCheck;
import org.jbasics.command.annotations.CommandParam;
import org.jbasics.configuration.properties.SystemProperty;
import org.jbasics.exception.DelegatedException;
import org.jbasics.pattern.strategy.ExecuteStrategy;
import org.jbasics.types.tuples.Triplet;
import org.jbasics.utilities.DataUtilities;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.util.Collection;
import java.util.Date;
import java.util.List;
public class CommandSpec implements ExecuteStrategy<Integer, CommandCall> {
private final String namespace;
private final String name;
private final String documentation;
private final Method commandMethod;
private final Triplet<String, Type, CommandParam>[] commandParameterMapping;
public CommandSpec(final String namespace, final String name, final Method commandMethod, final String documentation) {
this.namespace = ContractCheck.mustNotBeNullOrTrimmedEmpty(namespace, "namespace"); //$NON-NLS-1$
this.name = ContractCheck.mustNotBeNullOrTrimmedEmpty(name, "name"); //$NON-NLS-1$
this.commandMethod = ContractCheck.mustNotBeNull(commandMethod, "commandMethod"); //$NON-NLS-1$
if (!Modifier.isStatic(commandMethod.getModifiers())) {
throw new IllegalArgumentException("Command methods must be public static methods"); //$NON-NLS-1$
}
this.commandParameterMapping = scanParameters(commandMethod);
this.documentation = DataUtilities.coalesce(documentation, "<Documentation not available>");
}
private Triplet<String, Type, CommandParam>[] scanParameters(final Method m) {
final Annotation[][] annotations = m.getParameterAnnotations();
final Type[] params = m.getGenericParameterTypes();
@SuppressWarnings("unchecked") final Triplet<String, Type, CommandParam>[] result = new Triplet[params.length];
for (int i = 0; i < params.length; i++) {
final Annotation[] paramAnnotations = annotations[i];
CommandParam cmdParam = null;
for (final Annotation paramAnnotation : paramAnnotations) {
if (paramAnnotation instanceof CommandParam) {
cmdParam = (CommandParam) paramAnnotation;
break;
}
}
if (cmdParam == null) {
throw new RuntimeException("Missing @CommandParam annotation on the parmeter"); //$NON-NLS-1$
}
final String parameterName = cmdParam.value().trim();
if (parameterName.length() == 0) {
throw new RuntimeException("@CommandParam value cannot be an empty string it defines the name of the parameter"); //$NON-NLS-1$
}
result[i] = new Triplet<String, Type, CommandParam>(parameterName, params[i], cmdParam);
}
return result;
}
@Override
public Integer execute(final CommandCall request) {
final CommandCall cmdCall = ContractCheck.mustNotBeNull(request, "request"); //$NON-NLS-1$
final Object[] temp = new Object[this.commandParameterMapping.length];
for (int i = 0; i < temp.length; i++) {
final Triplet<String, Type, CommandParam> paramSpec = this.commandParameterMapping[i];
final CommandParam commandAnnotation = paramSpec.third();
CommandParameter value = cmdCall.getParameters().get(paramSpec.first());
if (value == null) {
String tempDefaultValue = null;
if (commandAnnotation.propertyName() != null && commandAnnotation.propertyName().trim().length() > 0) {
tempDefaultValue = SystemProperty.stringProperty(commandAnnotation.propertyName().trim(), null).value();
}
if (tempDefaultValue == null && commandAnnotation.defaultValue() != null && commandAnnotation.defaultValue().trim().length() > 0) {
value = CommandParameter.parseContent(commandAnnotation.value(), commandAnnotation.defaultValue().trim());
}
}
if (value != null) {
Class<?> paramType = null;
boolean isList = false;
final Type paramT = paramSpec.second();
if (paramT instanceof Class) {
paramType = (Class<?>) paramT;
} else if (paramT instanceof ParameterizedType) {
paramType = getListType((ParameterizedType) paramT);
isList = true;
}
if (CommandParameter.class.isAssignableFrom(paramType)) {
temp[i] = value;
} else if (String.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asStrings() : value.mustBeSingle().asString();
} else if (Boolean.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asBoolean() : value.mustBeSingle().asBoolean();
} else if (Integer.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asIntegers() : value.mustBeSingle().asInteger();
} else if (Double.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asDoubles() : value.mustBeSingle().asDouble();
} else if (BigInteger.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asBigIntegers() : value.mustBeSingle().asBigInteger();
} else if (BigDecimal.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asBigDecimals() : value.mustBeSingle().asBigDecimal();
} else if (URI.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asURIs() : value.mustBeSingle().asURI();
} else if (File.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asFiles() : value.mustBeSingle().asFile();
} else if (Date.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asDates() : value.mustBeSingle().asDate();
} else if (XMLGregorianCalendar.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asXmlDates() : value.mustBeSingle().asXmlDate();
} else if (Duration.class.isAssignableFrom(paramType)) {
temp[i] = isList ? value.asDurations() : value.mustBeSingle().asDuration();
} else {
temp[i] = isList ? value.asStaticValueOfMethodValues(paramType) : value.asStaticValueOfMethodValue(paramType);
}
} else if (!commandAnnotation.optional()) {
throw new RuntimeException("Missing mandatory argument " + paramSpec.first()); //$NON-NLS-1$
}
}
try {
return (Integer) this.commandMethod.invoke(null, temp);
} catch (final IllegalArgumentException e) {
throw DelegatedException.delegate(e);
} catch (final IllegalAccessException e) {
throw DelegatedException.delegate(e);
} catch (final InvocationTargetException e) {
throw DelegatedException.delegate(e);
} finally {
//
}
}
private final Class<?> getListType(final ParameterizedType type) {
final Type rawType = type.getRawType();
if (rawType == List.class || rawType == Collection.class) {
final Type implType = type.getActualTypeArguments()[0];
if (implType instanceof Class) {
return (Class<?>) implType;
}
}
return null;
}
public String getFullname() {
return getNamespace() + "/" + getName(); //$NON-NLS-1$
}
public String getNamespace() {
return this.namespace;
}
public String getName() {
return this.name;
}
public String getDocumentation() {
return this.documentation;
}
@Override
@SuppressWarnings("nls")
public String toString() {
final StringBuilder temp = new StringBuilder().append(this.namespace).append("/").append(this.name);
for (final Triplet<String, Type, CommandParam> paramSpec : this.commandParameterMapping) {
final Type t = paramSpec.second();
String typeName = null;
if (t instanceof Class) {
typeName = ((Class<?>) t).getSimpleName();
} else if (t instanceof ParameterizedType) {
final Class<?> tt = getListType((ParameterizedType) t);
if (tt != null) {
typeName = tt.getSimpleName() + "s"; //$NON-NLS-1$
}
}
if (paramSpec.third().optional()) {
temp.append(" [").append(paramSpec.first()).append(":").append(typeName).append("]");
} else {
temp.append(" ").append(paramSpec.first()).append(":").append(typeName);
}
}
return temp.toString();
}
}