All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.admin.rest.composite.metadata.RestMethodMetadata Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2012-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 * Portions Copyright [2017-2021] Payara Foundation and/or affiliates
 */
package org.glassfish.admin.rest.composite.metadata;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonException;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.container.Suspended;

import org.glassfish.admin.rest.OptionsCapable;
import org.glassfish.admin.rest.composite.RestCollection;
import org.glassfish.admin.rest.composite.RestModel;
import org.glassfish.admin.rest.utils.Util;

/**
 * This class encapsulates the metadata for the specified REST resource method.
 * @author jdlee
 */
public class RestMethodMetadata {
    private String httpMethod;
    private List queryParameters = new ArrayList();
    /**
     * The type of the incoming request body, if there is one.
     */
    private Type requestPayload;
    /**
     * The type of the response entity.  If isCollection is true, this value reflects the type of the items
     * in the collection.
     */
    private Type returnPayload;
    /**
     * Specifies whether or not the return payload is a collection (in which case returnPayload gives the
     * type of the items in the collection) or not.
     */
    private boolean isCollection = false;
    private final String path;
    private String[] produces;
    private String[] consumes;
    private final OptionsCapable context;

    public RestMethodMetadata(OptionsCapable context, Method method, Annotation designator) {
        this.context = context;
        this.httpMethod = designator.getClass().getInterfaces()[0].getSimpleName();
        this.returnPayload = calculateReturnPayload(method);
        this.path = getPath(method);

        Consumes cann = getAnnotation(Consumes.class, method);
        Produces pann = getAnnotation(Produces.class, method);
        if (cann != null) {
            consumes = cann.value();
        }
        if (pann != null) {
            produces = pann.value();
        }

        processParameters(method);
    }

    private  T getAnnotation(Class annoClass, Method method) {
        T annotation = (T)method.getAnnotation((Class)annoClass);
        if (annotation == null) {
            annotation = (T)method.getDeclaringClass().getAnnotation((Class)annoClass);
        }

        return annotation;
    }

    public String getHttpMethod() {
        return httpMethod;
    }

    public void setHttpMethod(String httpMethod) {
        this.httpMethod = httpMethod;
    }

    public List getQueryParameters() {
        return queryParameters;
    }

    public void setQueryParameters(List queryParameters) {
        this.queryParameters = queryParameters;
    }

    public Type getRequestPayload() {
        return requestPayload;
    }

    public void setRequestPayload(Type requestPayload) {
        this.requestPayload = requestPayload;
    }

    public Type getReturnPayload() {
        return returnPayload;
    }

    public void setReturnPayload(Type returnPayload) {
        this.returnPayload = returnPayload;
    }

    public boolean getIsCollection() {
        return isCollection;
    }

    public void setIsCollection(boolean isCollection) {
        this.isCollection = isCollection;
    }

    @Override
    public String toString() {
        try {
            return toJson().toString();
        } catch (JsonException ex) {
            return ex.getMessage();
        }
    }

    /**
     * Build and return a Json object representing the metadata for the resource method
     * @return
     * @throws JsonException
     */
    public JsonObject toJson() throws JsonException {
        JsonObjectBuilder o = Json.createObjectBuilder();
        if (path != null) {
            o.add("path", path);
        }

        JsonObjectBuilder queryParamJson = Json.createObjectBuilder();
        for (ParamMetadata pmd : queryParameters) {
            queryParamJson.add(pmd.getName(), pmd.toJson());
        }

        if (consumes != null) {
            JsonArrayBuilder array = Json.createArrayBuilder();
            for (String type : consumes) {
                array.add(type);
            }
            o.add("accepts", array);
        }
        if (produces != null) {
            JsonArrayBuilder array = Json.createArrayBuilder();
            for (String type : produces) {
                array.add(type);
            }
            o.add("produces", array);
        }

        o.add("queryParams", queryParamJson);

        if (requestPayload != null) {
            JsonObjectBuilder requestProps = Json.createObjectBuilder();
            requestProps.add("isCollection", isCollection);
            requestProps.add("dataType", getTypeString(requestPayload));
            requestProps.add("properties", getProperties(requestPayload));
            o.add("request", requestProps);
        }

        if (returnPayload != null) {
            JsonObjectBuilder returnProps = Json.createObjectBuilder();
            returnProps.add("isCollection", isCollection);
            returnProps.add("dataType", getTypeString(returnPayload));
            returnProps.add("properties", getProperties(returnPayload));
            o.add("response", returnProps);
        }

        return o.build();
    }

    /**
     * Get the return type of the method.  If the return type is a generic type, then extract the first generic type
     * from the declaration via reflection.  This method does not take into account types with multiple generic types,
     * but, given the style guide, this should not be an issue.  If the style guide is modified to allow non-RestModel and
     * non-RestCollection returns (for example), this may need to be modified.
     * @param method
     * @return
     */
    private Type calculateReturnPayload(Method method) {
        final Type grt = method.getGenericReturnType();
        Type value = method.getReturnType();
        if (ParameterizedType.class.isAssignableFrom(grt.getClass())) {
            final ParameterizedType pt = (ParameterizedType) grt;
            if (RestCollection.class.isAssignableFrom((Class) pt.getRawType())) {
                isCollection = true;
                value = Util.getFirstGenericType(grt);
            } else if (RestModel.class.isAssignableFrom((Class)pt.getRawType())) {
                value = Util.getFirstGenericType(grt);
            }
        }

        return value;
    }

    private Type calculateParameterType(Type paramType) {
        Type type;

        if (Util.isGenericType(paramType)) {
            ParameterizedType pt = (ParameterizedType) paramType;
            Class first = Util.getFirstGenericType(paramType);
            if (RestModel.class.isAssignableFrom(first) || RestCollection.class.isAssignableFrom(first)) {
//            if ((first instanceof RestModel.class) || (first instanceof RestCollection.class)) {
                type = first;
            } else {
                type = pt;
            }
        } else {
            type = paramType;
        }

        return type;
    }

    private String getPath(Method method) {
        Path p = method.getAnnotation(Path.class);
        if (p != null) {
            return p.value();
        } else {
            return null;
        }
    }

    /**
     * Process the parameters for the method.  Any parameter marked @PathParam is ignored, since JAX-RS
     * handles setting that value, meaning its presence need not be exposed to the client.  Any parameter marked with
     * @QueryParameter is stored in the queryParameter list.  Anything left is considered the
     * type of the request body. There should be only one of these.
     * @param method
     */
    private void processParameters(Method method) {
        Type[] paramTypes = method.getGenericParameterTypes();
        Annotation[][] paramAnnos = method.getParameterAnnotations();
        int paramCount = paramTypes.length;

        for (int i = 0; i < paramCount; i++) {
            boolean processed = false;
            boolean isPathParam = false;
            Type paramType = paramTypes[i];
            for (Annotation annotation : paramAnnos[i]) {
                processed =
                    (annotation instanceof Suspended) ||
                    (annotation instanceof PathParam);

                if (annotation instanceof QueryParam) {
                    queryParameters.add(new ParamMetadata(context,
                            paramType,
                            ((QueryParam)annotation).value(),
                            paramAnnos[i]));
                    processed = true;
                }
            }
            if (!processed && !isPathParam) {
                requestPayload = calculateParameterType(paramType);
            }
        }
    }

    /**
     * This method will analyze the getters of the given class to determine its properties.  Currently, for simplicity's
     * sake, only getters are checked.
     * @param type
     * @return
     * @throws JsonException
     */
    private JsonObject getProperties(Type type) throws JsonException {
        JsonObjectBuilder props = Json.createObjectBuilder();
        Class clazz;
        Map map = new HashMap();
        if (Util.isGenericType(type)) {
            ParameterizedType pt = (ParameterizedType)type;
            clazz = (Class)pt.getRawType();
//            if (RestModel.class.isAssignableFrom(clazz) || RestCollection.class.isAssignableFrom(clazz)) {
//                Object model = CompositeUtil.instance().getModel(clazz);
//                clazz = model.getClass();
//            }
        } else {
            clazz = (Class) type;
        }

        if (RestModel.class.isAssignableFrom(clazz) || RestCollection.class.isAssignableFrom(clazz)) {
            for (Method m : clazz.getMethods()) {
                String methodName = m.getName();
                if (methodName.startsWith("get")) {
                    String propertyName = methodName.substring(3, 4).toLowerCase(Locale.getDefault()) + methodName.substring(4);
                    map.put(propertyName, new ParamMetadata(context, m.getGenericReturnType(), propertyName, m.getAnnotations()));
                }
            }

            for (Map.Entry entry : map.entrySet()) {
                props.add(entry.getKey(), entry.getValue().toJson());
            }
        }

        return props.build();
    }

    protected String getTypeString(Type clazz) {
        StringBuilder sb = new StringBuilder();
        if (Util.isGenericType(clazz)) {
            ParameterizedType pt = (ParameterizedType)clazz;
            sb.append(((Class)pt.getRawType()).getSimpleName());
            Type [] typeArgs = pt.getActualTypeArguments();
            if ((typeArgs != null) && (typeArgs.length >= 1)) {
                String sep = "";
                sb.append("<");
                for (Type arg : typeArgs) {
                    sb.append(sep)
                            .append(((Class)arg).getSimpleName());
                    sep=",";
                }
                sb.append(">");
            }
        } else {
            sb.append(((Class)clazz).getSimpleName());
        }
        return sb.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy