/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.svcs.errorhandling.utils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Response.StatusType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.svcs.errorhandling.annotations.MessageBundle;
import com.emc.storageos.svcs.errorhandling.model.ExceptionMessagesProxy;
import com.emc.storageos.svcs.errorhandling.model.ServiceCoded;
import com.emc.storageos.svcs.errorhandling.model.StatusCoded;
import com.emc.storageos.svcs.errorhandling.resources.ServiceCode;
import com.sun.jersey.core.spi.scanning.PackageNamesScanner;
import com.sun.jersey.spi.scanning.AnnotationScannerListener;
public class Documenter {
private static final Logger _log = LoggerFactory.getLogger(Documenter.class);
/*
* The packages list got by @MessageBundle greps on 2/2017 WJE
* The below list includes all packages other than "com.emc.storgaeos.hds" and "com.emc.storgaeos.ceph"
* Adding "com.emc.storgaeos.hds" and "com.emc.storgaeos.ceph" is causing IndexOutOfBound exception when running the tests.
*/
public static final String[] PACKAGES = new String[] {
"com.emc.storageos.cimadapter.exceptions",
"com.emc.storageos.cinder.errorhandling",
"com.emc.storageos.computesystemcontroller.exceptions",
"com.emc.storageos.coordinator.exceptions",
"com.emc.storageos.customconfigcontroller.exceptions",
"com.emc.storageos.datadomain.restapi.errorhandling",
"com.emc.storageos.db.exceptions",
"com.emc.storageos.ecs.api",
"com.emc.storageos.exceptions",
"com.emc.storageos.glance.errorhandling",
"com.emc.storageos.imageservercontroller.exceptions",
"com.emc.storageos.isilon.restapi",
"com.emc.storageos.keystone.restapi.errorhandling",
"com.emc.storageos.management.backup.exceptions",
"com.emc.storageos.netapp",
"com.emc.storageos.netappc",
"com.emc.storageos.networkcontroller.exceptions",
"com.emc.storageos.plugins",
"com.emc.storageos.plugins.metering.vplex",
"com.emc.storageos.recoverpoint.exceptions",
"com.emc.storageos.scaleio",
"com.emc.storageos.security.exceptions",
"com.emc.storageos.security.geo.exceptions",
"com.emc.storageos.svcs.errorhandling.resources",
"com.emc.storageos.systemservices.exceptions",
"com.emc.storageos.vcentercontroller.exceptions",
"com.emc.storageos.vnx.xmlapi",
"com.emc.storageos.vnxe",
"com.emc.storageos.volumecontroller.impl.externaldevice",
"com.emc.storageos.volumecontroller.impl.plugins.discovery.smis",
"com.emc.storageos.volumecontroller.impl.smis",
"com.emc.storageos.volumecontroller.placement",
"com.emc.storageos.vplex.api",
"com.emc.storageos.workflow",
"com.emc.storageos.xiv.api",
"com.emc.storageos.xtremio.restapi.errorhandling" };
public static Collection<DocumenterEntry> createEntries() {
final List<Class<?>> list = getMessageBundleClasses();
_log.info("found these classes to document: {}", list);
final Collection<DocumenterEntry> entries = new ArrayList<DocumenterEntry>();
for (final Class<?> interfaze : list) {
final Object proxy = ExceptionMessagesProxy.create(interfaze);
final Method[] methods = interfaze.getDeclaredMethods();
for (final Method method : methods) {
StatusType status;
try {
final Object[] parameters = sampleParameters(method);
final ServiceCoded sce = (ServiceCoded) method.invoke(proxy, parameters);
if (sce instanceof StatusCoded) {
status = ((StatusCoded) sce).getStatus();
} else {
status = sce.isRetryable() ? Status.SERVICE_UNAVAILABLE
: Status.INTERNAL_SERVER_ERROR;
}
entries.add(new DocumenterEntry(interfaze, method, status, sce, parameters));
} catch (final Exception e) {
_log.error(String.format("Fail to create document entry for method: %s", method), e);
}
}
}
return entries;
}
@SuppressWarnings("unchecked")
public static List<Class<?>> getMessageBundleClasses() {
final PackageNamesScanner scanner = new PackageNamesScanner(PACKAGES);
final AnnotationScannerListener scannerListener = new AnnotationScannerListener(
MessageBundle.class);
scanner.scan(scannerListener);
final List<Class<?>> list = new ArrayList<Class<?>>();
for (final Class<?> clazz : scannerListener.getAnnotatedClasses()) {
if (clazz.isEnum()) {
continue;
}
list.add(clazz);
}
return list;
}
public static void main(final String[] args) throws FileNotFoundException,
NoSuchFieldException, SecurityException {
PrintStream out = System.out;
if (args.length != 0) {
final File targetDir = new File(args[0]);
final File targetFile = new File(targetDir, "errorhandling.txt");
out = new PrintStream(new FileOutputStream(targetFile), true);
}
document(out);
}
private static void document(PrintStream out) throws NoSuchFieldException {
final Map<ServiceCode, List<DocumenterEntry>> codeToEntries = new TreeMap<ServiceCode, List<DocumenterEntry>>();
for (final ServiceCode value : ServiceCode.values()) {
codeToEntries.put(value, new ArrayList<DocumenterEntry>());
}
for (final DocumenterEntry entry : createEntries()) {
codeToEntries.get(entry.getCode()).add(entry);
}
for (final Entry<ServiceCode, List<DocumenterEntry>> pair : codeToEntries.entrySet()) {
final ServiceCode code = pair.getKey();
final List<DocumenterEntry> messages = pair.getValue();
Collections.sort(messages);
// no point documenting ServiceCodes that aren't in use
if (messages.isEmpty()) {
continue;
}
documentServiceCode(out, code, messages);
documentMessages(out, messages);
out.println();
}
}
private static void documentServiceCode(PrintStream out, final ServiceCode code,
final List<DocumenterEntry> messages) throws NoSuchFieldException {
final StatusType status = messages.get(0).getStatus();
final boolean retryable = status.getStatusCode() == 503;
out.println("Service Code: " + code.getCode());
out.println("Name: " + code.name());
out.println("Description: " + code.getSummary(Locale.ENGLISH));
out.println("Retryable: " + retryable);
out.println("Deprecated: " + checkDeprecation(ServiceCode.class.getField(code.name())));
out.println("HTTP Status: " + status.getStatusCode() + " " + status.getReasonPhrase());
out.println();
}
private static void documentMessages(PrintStream out, final List<DocumenterEntry> messages) {
for (final DocumenterEntry message : messages) {
documentMessage(out, message);
}
}
private static void documentMessage(PrintStream out, final DocumenterEntry message) {
final Class<?> interfaze = message.getInterfaze();
final Method method = message.getMethod();
final String bundle = MessageUtils.bundleNameForClass(interfaze);
final String key = method.getName();
final boolean deprecated = checkDeprecation(interfaze, method);
final String pattern = Messages.getPattern(Locale.ENGLISH, key, bundle);
final String example = message.getMessage();
out.println(" Message Bundle: " + bundle);
out.println(" Message Key: " + key);
out.println(" Message Parameters: " + documentParameters(method));
out.println(" Message Deprecated: " + deprecated);
out.println(" Message Pattern: " + pattern);
out.println(" Message Example: " + example);
out.println();
}
private static boolean checkDeprecation(final AnnotatedElement... elements) {
for (final AnnotatedElement element : elements) {
if (element.isAnnotationPresent(Deprecated.class)) {
return true;
}
}
return false;
}
private static String documentParameters(final Method method) {
final StringBuilder builder = new StringBuilder();
for (final Class<?> type : method.getParameterTypes()) {
if (builder.length() > 0) {
builder.append(", ");
}
if (type.isArray()) {
builder.append(type.getComponentType().getSimpleName());
builder.append("[]");
} else {
builder.append(type.getSimpleName());
}
}
return builder.toString();
}
public static Object[] sampleParameters(final Method method) {
final Class<?>[] types = method.getParameterTypes();
final Object[] result = new Object[types.length];
for (int i = 0; i < types.length; ++i) {
result[i] = sampleParameter(method, types[i], i);
}
return result;
}
@SuppressWarnings("rawtypes")
private static Object sampleParameter(final Method method, final Class<?> clazz, final int index) {
if (clazz.equals(int.class)) {
return index;
} else if (clazz.equals(long.class)) {
return (long) index;
} else if (clazz.equals(double.class)) {
return (double) index;
} else if (clazz.equals(float.class)) {
return (float) index;
} else if (clazz.equals(char.class)) {
return '0' + index;
} else if (clazz.equals(String.class)) {
return "string" + index;
} else if (clazz.equals(URI.class)) {
try {
return new URI("sos:uri:" + index);
} catch (final URISyntaxException e) {
_log.error(String.format("Fail to instantiate URI with \"sos:uri:%s\"", index), e);
}
} else if (clazz.isAssignableFrom(Date.class)) {
final Calendar calendar = Calendar.getInstance();
calendar.set(2000, 1, 1, 0, 0, 0);
return calendar.getTime();
} else if (clazz.isAssignableFrom(Throwable.class)) {
return new Throwable("throwable" + index);
} else if (clazz.isAssignableFrom(Exception.class)) {
return new Exception("exception" + index);
} else if (clazz.isAssignableFrom(Set.class)) {
return new HashSet();
} else if (clazz.isAssignableFrom(List.class)) {
return new ArrayList();
} else if (clazz.isAssignableFrom(Map.class)) {
return new HashMap();
} else if (clazz.isAssignableFrom(StatusType.class)) {
return Status.BAD_REQUEST;
}
try {
return clazz.newInstance();
} catch (Exception e) {
return null;
}
}
protected static class DocumenterEntry implements Comparable<DocumenterEntry> {
private final Class<?> interfaze;
private final Method method;
private final Object[] parameters;
private final StatusType status;
private final ServiceCode code;
private final String summary;
private final String message;
@Override
public String toString() {
return "DocumenterEntry[" + interfaze.getCanonicalName() + "," + method.getName() + ","
+ getParametersAsString() + "," + status + "," + code + "," + summary + ","
+ message + "]";
}
@Override
public int compareTo(final DocumenterEntry that) {
return this.toString().compareTo(that.toString());
}
public Class<?> getInterfaze() {
return interfaze;
}
public Method getMethod() {
return method;
}
public StatusType getStatus() {
return status;
}
public ServiceCode getCode() {
return code;
}
public String getSummary() {
return summary;
}
public String getMessage() {
return message;
}
public Object[] getParameters() {
return Arrays.copyOf(parameters, parameters.length);
}
public String getParametersAsString() {
final StringBuilder builder = new StringBuilder();
for (final Class<?> type : method.getParameterTypes()) {
if (builder.length() > 0) {
builder.append(", ");
}
if (type.isArray()) {
builder.append(type.getComponentType().getSimpleName());
builder.append("[]");
} else {
builder.append(type.getSimpleName());
}
}
return builder.toString();
}
public DocumenterEntry(final Class<?> interfaze, final Method method,
final StatusType status, final ServiceCoded sce, final Object[] parameters) {
this.interfaze = interfaze;
this.method = method;
this.status = status;
this.code = sce.getServiceCode();
this.summary = code.getSummary();
this.message = sce.getMessage();
this.parameters = (parameters != null) ? Arrays.copyOf(parameters, parameters.length) : null;
}
}
}