com.linkedin.restli.examples.greetings.server.GreetingsResourceCodeGenerator Maven / Gradle / Ivy
Show all versions of restli-int-test-server Show documentation
/*
Copyright (c) 2012 LinkedIn Corp.
Licensed 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 com.linkedin.restli.examples.greetings.server;
import com.linkedin.common.callback.Callback;
import com.linkedin.parseq.BaseTask;
import com.linkedin.parseq.Context;
import com.linkedin.parseq.Task;
import com.linkedin.parseq.Tasks;
import com.linkedin.parseq.promise.Promise;
import com.linkedin.parseq.promise.Promises;
import com.linkedin.parseq.promise.SettablePromise;
import com.linkedin.restli.examples.greetings.api.Greeting;
import com.linkedin.restli.internal.server.model.ResourceMethodDescriptor.InterfaceType;
import com.linkedin.restli.server.annotations.CallbackParam;
import com.linkedin.restli.server.annotations.ParSeqContext;
import com.linkedin.restli.server.annotations.RestLiCollection;
import com.linkedin.restli.server.annotations.RestMethod;
import com.linkedin.restli.server.resources.BaseResource;
import com.linkedin.restli.server.resources.KeyValueResource;
import com.linkedin.restli.server.resources.ResourceContextHolder;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.Generated;
/**
* Generate concrete implementations of the greetings resource, {@link GreetingsResourceImpl}, with
* all methods and annotations copied. This allows us to exhaustively test all interface types.
*
* See the generateGreetings
task in build.gradle
and the
* TestGreetingsClientFlavors
test case.
*
* @author jnwang
*/
public class GreetingsResourceCodeGenerator
{
private static final Class> IMPL_CLASS = GreetingsResourceImpl.class;
private static final String NAMESPACE = "com.linkedin.restli.examples.greetings.client";
private static final Class> RES_ANNOTATION = RestLiCollection.class;
private static final Class> KEY = Long.class;
private static final Class> VALUE = Greeting.class;
private static final String PACKAGE = "com.linkedin.restli.examples.greetings.server";
private static final String BASIC_CLASS_NAME = "GreetingsResource";
private static final String BASIC_RES_NAME = "greetings";
private final Class> _templateClass;
private final InterfaceType _type;
private final String _suffix;
private final boolean _useParSeqCtx;
private final String _resourceName;
private final String _className;
private GreetingsResourceCodeGenerator(final Class> templateClass,
final InterfaceType type,
final String suffix,
final boolean useParSeqCtx)
{
_templateClass = templateClass;
_type = type;
_suffix = suffix;
_useParSeqCtx = useParSeqCtx;
_resourceName = BASIC_RES_NAME + _suffix;
_className = BASIC_CLASS_NAME + _suffix;
}
/**
* @param args
* template, interface type, suffix, use ParSeq context
* @throws ClassNotFoundException
*/
public static void main(final String[] args) throws ClassNotFoundException
{
// want a hard failure if something goes wrong, so gradle stops
if (args.length != 4)
throw new IllegalArgumentException("Bad arguments to code generator");
final Class> template = Class.forName(args[0]);
final InterfaceType type = InterfaceType.valueOf(args[1]);
final String suffix = args[2];
final boolean useParSeqCtx = Boolean.valueOf(args[3]);
final GreetingsResourceCodeGenerator gen =
new GreetingsResourceCodeGenerator(template, type, suffix, useParSeqCtx);
gen.printAll(System.out);
}
private void printAll(final PrintStream out)
{
// print out the main body later
final ByteArrayOutputStream bodyOutStream = new ByteArrayOutputStream();
final PrintStream bodyOut = new PrintStream(bodyOutStream);
printClassHeader(bodyOut);
bodyOut.println();
printAllMethods(bodyOut);
bodyOut.println("}"); // end class
// package and imports first
out.printf("package %s;%n", PACKAGE);
out.println();
for (final String c : _importedFull)
{
// don't make import statements for arrays, the base class will be imported separately
if (!(c.endsWith("[]")))
{
out.printf("import %s;%n", c);
}
}
out.println();
// main code body
out.print(new String(bodyOutStream.toByteArray()));
}
/**
* class annotation, class definition, private variables
*/
private void printClassHeader(final PrintStream out)
{
out.printf("/* WARNING: GENERATED CODE - DO NOT MODIFY BY HAND */%n%n");
// generated annotation
out.printf("@%s(\"%s\")%n", className(Generated.class), getClass().getName());
// collection resource annotation
out.printf("@%s(name = \"%s\", namespace = \"%s\")%n",
className(RES_ANNOTATION),
_resourceName,
NAMESPACE);
// class declaration
out.printf("public class %s extends ", _className);
if (_templateClass.isInterface())
out.printf("%s implements ", className(ResourceContextHolder.class));
out.printf("%s<%s, %s>", className(_templateClass), className(KEY), className(VALUE));
out.println();
out.println("{");
// private constants
out.printf("private static final GreetingsResourceImpl _impl = new GreetingsResourceImpl(\"%s\");%n",
_resourceName);
if (_type == InterfaceType.CALLBACK || _type == InterfaceType.PROMISE && !_useParSeqCtx)
{
out.printf("private static final %s _scheduler = %s.newScheduledThreadPool(1);%n",
className(ScheduledExecutorService.class),
className(Executors.class));
out.println("private static final int DELAY = 100;");
}
}
private void printAllMethods(final PrintStream out)
{
// avoid stuff declared in Object and superclasses
final Method[] methods = IMPL_CLASS.getMethods();
for (final Method method : methods)
if (method.getDeclaringClass() == IMPL_CLASS)
printMethod(out, method);
}
private void printMethod(final PrintStream out, final Method method)
{
final Type returnType = method.getGenericReturnType();
// method annotations
final Annotation[] annos = method.getAnnotations();
for (final Annotation anno : annos)
{
if (RestMethod.class.equals(anno.annotationType().getEnclosingClass()))
if (!_templateClass.equals(KeyValueResource.class))
continue;
out.println(toStrAnnotation(anno));
}
// method header
out.print("public ");
// return type
switch (_type)
{
case CALLBACK:
out.print("void");
break;
case PROMISE:
out.print(genericWrapper(Promise.class, returnType));
break;
case SYNC:
out.print(toStrGenericType(returnType));
break;
case TASK:
out.print(genericWrapper(Task.class, returnType));
break;
}
out.printf(" %s(", method.getName());
printArgumentList(out, method);
out.print(")");
printThrowsClause(out, method);
out.println();
out.println("{"); // begin method body
printMethodBody(out, method);
out.println("}"); // end method
out.println();
}
private void printArgumentList(final PrintStream out, final Method method)
{
final Type[] argTypes = method.getGenericParameterTypes();
final Annotation[][] argAnnos = method.getParameterAnnotations();
boolean first = true;
for (int i = 0; i < argTypes.length; i++)
{
final Type argType = argTypes[i];
if (argType == BaseResource.class)
continue;
if (!first)
out.print(", ");
first = false;
for (final Annotation anno : argAnnos[i])
out.print(toStrAnnotation(anno) + " ");
out.printf("final %s %s", toStrGenericType(argType), (char) ('a' + i));
}
if (_type == InterfaceType.CALLBACK)
{
final Type returnType = method.getGenericReturnType();
if (!first)
out.print(", ");
out.printf("@%s final %s callback",
className(CallbackParam.class),
genericWrapper(Callback.class, returnType));
}
else if (_useParSeqCtx)
{
if (!first)
out.print(", ");
out.printf("@%s final %s psContext", className(ParSeqContext.class), className(Context.class));
}
}
private void printThrowsClause(final PrintStream out, final Method method)
{
final Class>[] exceptions = method.getExceptionTypes();
if (exceptions.length > 0)
{
out.print(" throws ");
for (int i = 0; i < exceptions.length; i++)
{
if (i != 0)
out.print(", ");
out.print(className(exceptions[i]));
}
}
}
private void printMethodBody(final PrintStream out, final Method method)
{
final Type returnType = method.getGenericReturnType();
switch (_type)
{
case CALLBACK:
out.println("final Runnable requestHandler = new Runnable() {");
out.println("public void run () {");
out.println("try {");
if (returnType == void.class)
{
printImplCall(out, method);
out.println(";");
out.println("callback.onSuccess(null);");
}
else
{
out.print("callback.onSuccess(");
printImplCall(out, method);
out.println(");");
}
out.println("} catch (final Throwable throwable) {");
out.println("callback.onError(throwable);");
out.println("}"); // try catch
out.println("}"); // run
out.println("};"); // runnable
out.printf("_scheduler.schedule(requestHandler, DELAY, %s.MILLISECONDS);",
className(TimeUnit.class));
break;
case PROMISE:
if (_useParSeqCtx)
{
out.printf("final %s result = %s.settable();%n",
genericWrapper(SettablePromise.class, returnType),
className(Promises.class));
out.println("final Runnable requestHandler = new Runnable() {");
out.println("public void run () {");
out.println("try {");
if (returnType == void.class)
{
printImplCall(out, method);
out.println(";");
out.println("result.done(null);");
}
else
{
out.print("result.done(");
printImplCall(out, method);
out.println(");");
}
out.println("} catch (final Throwable throwable) {");
out.println("result.fail(throwable);");
out.println("}"); // try catch
out.println("}"); // run
out.println("};"); // runnable
out.printf("psContext.run(%s.action(\"restli-%s\", requestHandler));%n",
className(Tasks.class),
method.getName());
out.println("return result;");
}
else
{
out.printf("final %s result = %s.settable();%n",
genericWrapper(SettablePromise.class, returnType),
className(Promises.class));
out.println("final Runnable requestHandler = new Runnable() {");
out.println("public void run () {");
out.println("try {");
if (returnType == void.class)
{
printImplCall(out, method);
out.println(";");
out.println("result.done(null);");
}
else
{
out.print("result.done(");
printImplCall(out, method);
out.println(");");
}
out.println("} catch (final Throwable throwable) {");
out.println("result.fail(throwable);");
out.println("}"); // try catch
out.println("}"); // run
out.println("};"); // runnable
out.printf("_scheduler.schedule(requestHandler, DELAY, %s.MILLISECONDS);",
className(TimeUnit.class));
out.println("return result;");
}
break;
case SYNC:
if (!returnType.equals(void.class))
out.print("return ");
printImplCall(out, method);
out.println(";");
break;
case TASK:
out.printf("return new %s()%n", genericWrapper(BaseTask.class, returnType));
out.println("{");
out.printf("protected %s run(final %s context) throws Exception%n",
genericWrapper(Promise.class, returnType),
className(Context.class));
out.println("{");
if (returnType.equals(void.class))
{
printImplCall(out, method);
out.println(";");
out.printf("return %s.value(null);%n", className(Promises.class));
}
else
{
out.printf("return %s.value(", className(Promises.class));
printImplCall(out, method);
out.println(");");
}
out.println("}");
out.println("};");
break;
}
}
/**
* _impl.method(a, b, ...)
*/
private void printImplCall(final PrintStream out, final Method method)
{
final Type[] argTypes = method.getGenericParameterTypes();
out.printf("_impl.%s(", method.getName());
for (int i = 0; i < argTypes.length; i++)
{
if (i > 0)
out.print(", ");
if (argTypes[i] == BaseResource.class)
out.printf("%s.this", _className);
else
out.print((char) ('a' + i));
}
out.print(")");
}
/**
* @return the source code corresponding to the given type
*/
private String toStrGenericType(final Type t)
{
return toStrGenericType(t, false);
}
private static final Map, String> PRIMITIVES_TO_CLASSES;
static
{
final Map, String> map = new HashMap, String>();
map.put(byte.class, "Byte");
map.put(short.class, "Short");
map.put(int.class, "Integer");
map.put(long.class, "Long");
map.put(float.class, "Float");
map.put(double.class, "Double");
map.put(boolean.class, "Boolean");
map.put(char.class, "Character");
map.put(void.class, "Void");
PRIMITIVES_TO_CLASSES = Collections.unmodifiableMap(map);
}
/**
* @param coercePrimitives
* should primitives be converted to their corresponding objects, e.g. int => Integer
* @return source code for the given type
*/
private String toStrGenericType(final Type t, final boolean coercePrimitives)
{
if (t instanceof Class)
{
final Class> c = (Class>) t;
if (c.isPrimitive() && coercePrimitives)
{
if (PRIMITIVES_TO_CLASSES.containsKey(c))
return PRIMITIVES_TO_CLASSES.get(c);
else
// map should be complete
throw new AssertionError("unknown primitive: " + c.toString());
}
else
{
return className((Class>) t);
}
}
if (t instanceof ParameterizedType)
{
final ParameterizedType param = (ParameterizedType) t;
final Type[] args = param.getActualTypeArguments();
final StringBuilder result = new StringBuilder();
result.append(toStrGenericType(param.getRawType()));
result.append('<'); // begin type params
for (int i = 0; i < args.length; i++)
{
if (i > 0)
result.append(", ");
result.append(toStrGenericType(args[i], true));
}
result.append('>'); // end type params
return result.toString();
}
if (t instanceof GenericArrayType)
{
final GenericArrayType type = (GenericArrayType) t;
return toStrGenericType(type.getGenericComponentType()) + "[]";
}
throw new UnsupportedOperationException("Don't know how to handle " + t);
}
/**
* @return the source code corresponding to the annotation
*/
// NOTE does not handle all cases, just enough to work
private String toStrAnnotation(final Annotation anno)
{
final StringBuilder sb = new StringBuilder();
sb.append('@');
final Class extends Annotation> annoType = anno.annotationType();
sb.append(className(annoType));
final Method[] methods = annoType.getDeclaredMethods();
final Map values = new TreeMap();
for (final Method method : methods)
{
try
{
final Object value = method.invoke(anno);
if (value.equals(method.getDefaultValue()))
continue;
final String name = method.getName();
if (value instanceof String)
values.put(name, "\"" + value + "\"");
else if (value instanceof Enum)
values.put(name, className(value.getClass()) + "." + value);
else if (value instanceof Class)
values.put(name, className((Class>) value) + ".class");
else
values.put(name, value.toString());
}
catch (final IllegalAccessException e)
{
throw new AssertionError(e);
}
catch (final InvocationTargetException e)
{
throw new AssertionError(e);
}
}
// omit parens when no values
if (values.size() > 0)
sb.append('('); // begin annotation values
boolean first = true;
for (final Map.Entry entry : values.entrySet())
{
if (!first)
sb.append(", ");
first = false;
final String key = entry.getKey();
// omit "value = " when only one
if (values.size() == 1 && key.equals("value"))
{
sb.append(entry.getValue());
}
else
{
sb.append(entry.getKey());
sb.append(" = ");
sb.append(entry.getValue());
}
}
if (values.size() > 0)
sb.append(')'); // end annotation values
return sb.toString();
}
/**
* @return genericClass<typeArg>, using the boxed version of typeArg if it is a primitive
*/
private String genericWrapper(final Class> genericClass, final Type typeArg)
{
return String.format("%s<%s>", className(genericClass), toStrGenericType(typeArg, true));
}
private final Set _importedShort = new TreeSet();
private final Set _importedFull = new TreeSet();
/**
* Import the class and give the short name if possible
*/
private String className(final Class> c)
{
if (c.isPrimitive())
return c.getCanonicalName();
if (_importedFull.contains(c.getCanonicalName()))
return c.getSimpleName();
final String simple = c.getSimpleName();
if (_importedShort.contains(simple))
return c.getCanonicalName(); // can't import, name conflict
_importedFull.add(c.getCanonicalName());
_importedShort.add(c.getSimpleName());
return c.getSimpleName();
}
}