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

org.glassfish.admin.rest.composite.CompositeUtil Maven / Gradle / Ivy

The newest version!
/*
 * 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 [2016-2021] [Payara Foundation and/or affiliates]
package org.glassfish.admin.rest.composite;

import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.admin.report.ActionReporter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Level;
import jakarta.json.JsonArray;
import jakarta.json.JsonException;
import jakarta.json.JsonNumber;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.JsonValue.ValueType;
import javax.security.auth.Subject;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorContext;
import jakarta.validation.ValidatorFactory;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import org.glassfish.admin.rest.RestExtension;
import org.glassfish.admin.rest.RestLogging;
import org.glassfish.admin.rest.composite.metadata.AttributeReference;
import org.glassfish.admin.rest.composite.metadata.DefaultBeanReference;
import org.glassfish.admin.rest.composite.metadata.HelpText;
import org.glassfish.admin.rest.utils.JsonUtil;
import org.glassfish.admin.rest.utils.ResourceUtil;
import org.glassfish.admin.rest.utils.SseCommandHelper;
import org.glassfish.admin.rest.utils.StringUtil;
import org.glassfish.admin.rest.utils.Util;
import org.glassfish.admin.rest.utils.xml.RestActionReporter;
import org.glassfish.api.ActionReport.ExitCode;
import org.glassfish.api.admin.ParameterMap;
import org.glassfish.hk2.api.ActiveDescriptor;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.glassfish.hk2.utilities.BuilderHelper;
import org.glassfish.internal.api.Globals;
import org.glassfish.jersey.media.sse.EventOutput;

import static org.objectweb.asm.Opcodes.*;

import org.jvnet.hk2.config.Attribute;
import org.jvnet.hk2.config.MessageInterpolatorImpl;

/**
 * @author jdlee
 */
public class CompositeUtil {
    private static final Map> generatedClasses = new HashMap>();
    private static final Map> modelExtensions = new HashMap>();
    private boolean extensionsLoaded = false;
    private static volatile Validator beanValidator = null;
    private static final LocalStringManagerImpl adminStrings = new LocalStringManagerImpl(CompositeUtil.class);

    private CompositeUtil() {
    }

    private static class LazyHolder {
        public static final CompositeUtil INSTANCE = new CompositeUtil();
    }

    public static CompositeUtil instance() {
        return LazyHolder.INSTANCE;
    }

    /**
     * This method will return a generated concrete class that implements the interface requested, as well as any
     * interfaces intended to extend the base model interface.  Model extensions must be annotated with
     *
     * @param modelIface   The base interface for the desired data model
     * @return An instance of a concrete class implementing the requested interfaces
     * @throws Exception
     * @RestModelExtension, and must be compiled with rest-annotation-processor library on the compile classpath
     * for metadata generation.
     */
    public synchronized  T getModel(Class modelIface) {
        String className = modelIface.getName() + "Impl";
        if (!generatedClasses.containsKey(className)) {
            Map> properties = new HashMap>();

            Set> interfaces = getModelExtensions(modelIface);
            interfaces.add(modelIface);

            for (Class iface : interfaces) {
                analyzeInterface(iface, properties);
            }
            ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            visitClass(classWriter, className, interfaces, properties);

            for (Map.Entry> entry : properties.entrySet()) {
                String name = entry.getKey();
                final Map property = entry.getValue();
                Class type = (Class) property.get("type");
                createField(classWriter, name, type);
                createGettersAndSetters(classWriter, modelIface, className, name, property);

            }

            createConstructor(classWriter, className, properties);
            classWriter.visitEnd();
            Class newClass;
            try {
                newClass = defineClass(modelIface, className, classWriter.toByteArray());
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
            generatedClasses.put(className, newClass);
        }
        try {
            return (T) generatedClasses.get(className).newInstance();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

        public Set> getRestModels() {
        Set>classes = new HashSet>();
        for (ActiveDescriptor ad : Globals.getDefaultBaseServiceLocator()
                .getDescriptors(BuilderHelper.createContractFilter(RestModel.class.getName()))) {
            try {
                classes.add(CompositeUtil.instance().getModel(Class.forName(ad.getImplementation())).getClass());
            } catch (ClassNotFoundException ex) {
                RestLogging.restLogger.log(Level.SEVERE, null, ex);
            }
        }

        return classes;
    }

    /**
     * Find and execute all resource extensions for the specified base resource and HTTP method
     * TODO: method enum?
     *
     * @param baseClass
     * @param data
     * @param method
     */
    public Object getResourceExtensions(Class baseClass, Object data, String method) {
        List extensions = new ArrayList();

        for (RestExtension extension : Globals.getDefaultHabitat().getAllServices(RestExtension.class)) {
            if (baseClass.getName().equals(extension.getParent())) {
                extensions.add(extension);
            }
        }

        if ("get".equalsIgnoreCase(method)) {
            handleGetExtensions(extensions, data);
        } else if ("post".equalsIgnoreCase(method)) {
            return handlePostExtensions(extensions, data);
        }

        return void.class;
    }

    public ParameterMap addToParameterMap(ParameterMap parameters, String basePath,
            Class configBean, Object source, Subject subject) {
        String name;
        Map currentValues =
                Util.getCurrentValues(basePath, Globals.getDefaultHabitat(), subject);
        for (Method cbMethod : configBean.getMethods()) {
            name = cbMethod.getName();
            if (name.startsWith("set")/* && (cbMethod.getAnnotation(Attribute.class) !=null)*/) {
                String getterName = "get" + name.substring(3, 4).toUpperCase(Locale.getDefault()) + name.substring(4);
                try {
                    Method getter = source.getClass().getMethod(getterName);
                    final String key = ResourceUtil.convertToXMLName(name.substring(3));
                    Object value = null;
                    try {
                        value = getter.invoke(source);
                    } catch (Exception ex) {
                        RestLogging.restLogger.log(Level.SEVERE, null, ex);
                    }
                    if (value != null) {
                        String currentValue = currentValues.get(basePath + key);

                        if ((currentValue == null) || "".equals(value) || (!currentValue.equals(value))) {
                            parameters.add("DEFAULT", basePath + "." + key + "=" + value);
                        }
                    }
                } catch (NoSuchMethodException ex) {
                    RestLogging.restLogger.log(Level.FINE, null, ex);
                }
            }
        }

        return parameters;
    }

    /**
     * Convert the given RestModel encoded as Json to a live Java Object.
     *
     * @param locale
     * @param modelClass The target RestModel type
     * @param json       The json encoding of the object
     * @return
     */
    public  T unmarshallClass(Locale locale, Class modelClass, JsonObject json) throws JsonException {
        T model = getModel(modelClass);
        for (Method setter : getSetters(modelClass)) {
            String name = setter.getName();
            String attribute = name.substring(3, 4).toLowerCase(Locale.getDefault()) + name.substring(4);
            Type param0 = setter.getGenericParameterTypes()[0];
            Class class0 = setter.getParameterTypes()[0];
            if (json.containsKey(attribute)) {
                java.lang.Object o = json.get(attribute);
                if (JsonArray.class.isAssignableFrom(o.getClass())) {
                    Object values = processJsonArray(locale, param0, (JsonArray) o);
                    invoke(locale, setter, attribute, model, values);
                } else if (JsonObject.class.isAssignableFrom(o.getClass())) {
                    invoke(locale, setter, attribute, model, unmarshallClass(locale, class0, (JsonObject) o));
                } else if (JsonString.class.isAssignableFrom(o.getClass())){
                    System.out.println(o);
                    invoke(locale, setter, attribute, model, ((JsonString)o).getString());
                } else if (JsonNumber.class.isAssignableFrom(o.getClass())){
                    invoke(locale, setter, attribute, model, ((JsonNumber) o).numberValue());
                } else {
                    if ("null".equals(o.toString())) {
                        o = null;
                    } else if ("true".equals(o.toString())){
                        o = true;
                    } else if ("false".equals(o.toString())) {
                        o = false;
                    }
                    if (!isUnmodifiedConfidentialProperty(modelClass, name, o)) {
                        invoke(locale, setter, attribute, model, o);
                        }
                    }
                }
            }
        return model;
    }

    private boolean isUnmodifiedConfidentialProperty(Class modelClass, String setterMethodName, Object value) {
        if (!(value instanceof String)) {
            return false;
        }
        String s = (String)value;
        if (!JsonUtil.CONFIDENTIAL_PROPERTY_SET.equals(s)) {
            return false;
        }
        String getterMethodName = "g" + setterMethodName.substring(1);
        return JsonUtil.isConfidentialProperty(modelClass, getterMethodName);
    }

    /**
     * Turns a JsonArray into an array or a list of the unboxed type it contains
     * @param locale
     * @param param0 The type of the array or list to create i.e. java.lang.String[]
     * @param array
     * @return
     * @throws JsonException 
     */
    private Object processJsonArray(Locale locale, Type param0, JsonArray array) throws JsonException {
        Type type;
        boolean isArray = false;
        if (ParameterizedType.class.isAssignableFrom(param0.getClass())) {
            type = ((ParameterizedType) param0).getActualTypeArguments()[0];
        } else {
            isArray = ((Class)param0).isArray();
            type = ((Class)param0).getComponentType();
        }
        // TODO: We either have a List or T[]. While this works, perhaps we should only support List. It's cleaner.
        Object values = isArray ?
                Array.newInstance((Class) type, array.size()) :
                new ArrayList();

        for (int i = 0; i < array.size(); i++) {
            JsonValue element = array.get(i);
            if (JsonObject.class.isAssignableFrom(element.getClass())) {
                if (isArray) {
                    Array.set(values, i, unmarshallClass(locale, (Class) type, (JsonObject) element));
                } else {
                    ((List)values).add(unmarshallClass(locale, (Class) type, (JsonObject) element));
                }
            } else {
                if (isArray) {
                    ValueType jsonvalue = element.getValueType();
                    switch (jsonvalue){
                        case STRING:
                            Array.set(values, i, element.toString());
                            break;
                        case NUMBER:
                            Array.set(values, i, ((JsonNumber)element).numberValue());
                            break;
                        case NULL:
                            Array.set(values, i, null);
                            break;
                        case TRUE:
                            Array.set(values, i, true);
                            break;
                        case FALSE:
                            Array.set(values, i, false);
                            break;
                        case ARRAY:
                            Array.set(values, i, processJsonArray(locale, param0, (JsonArray) element));
                            break;
                        default:
                            //Should never get here
                            throw new JsonException("Json Element " + element + "not valid valuetype: " + jsonvalue);
                    }
                    
                } else {
                    ((List)values).add(element);
                }
            }
        }
        return values;
    }

    private void invoke(Locale locale, Method m, String attribute, Object o, Object... args) {
        try {
            m.invoke(o, args);
        } catch (IllegalArgumentException iae) {
            // TODO: i18n
            String message = "An exception occured while trying to set the value for the property '" + 
                    attribute + "': " + iae.getLocalizedMessage();
            throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(message).build());
        } catch (Exception e) {
            String message = "An exception occured while trying to set the value for the property '" + 
                    attribute + "': " + e.getLocalizedMessage();
            throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(message).build());
        }
    }

    /**
     * If the HelpText annotation is in the list of Annotations, return the value from the
     * specified bundle for the given key.
     *
     * @param annos
     * @return
     */
    public String getHelpText(Annotation[] annos) {
        String helpText = null;
        if (annos != null) {
            for (Annotation annotation : annos) {
                if (HelpText.class.isAssignableFrom(annotation.getClass())) {
                    HelpText ht = (HelpText) annotation;
                    ResourceBundle bundle = ResourceBundle.getBundle(ht.bundle(), Locale.getDefault());
                    helpText = bundle.getString(ht.key());
                }
            }
        }

        return helpText;
    }

    public  Set> validateRestModel(Locale locale, T model) {
        initBeanValidator();

        Set> constraintViolations = beanValidator.validate(model);
        if (constraintViolations == null || constraintViolations.isEmpty()) {
            return Collections.emptySet();
        }

        return constraintViolations;
    }

    public  String getValidationFailureMessages(Locale locale, Set> constraintViolations, T model) {
        StringBuilder msg = new StringBuilder(adminStrings.getLocalString("rest.model.validationFailure",
                "Properties for model {0} violate the following constraints: ",
                model.getClass().getSimpleName()));
        String sep = "";
        String violationMsg = adminStrings.getLocalString("rest.model.validationFailure.reason",
                "on property [ {1} ] violation reason [ {0} ]");
        for (ConstraintViolation cv : constraintViolations) {
            msg.append(sep)
                    .append(MessageFormat.format(violationMsg, cv.getMessage(), cv.getPropertyPath()));

            sep = "\n";
        }
        return msg.toString();
    }

    /**
     * Apply changes to domain.xml
     *
     * @param changes
     * @param basePath
     */
    public void applyChanges(Map changes, String basePath, Subject subject) {
        RestActionReporter ar = Util.applyChanges(changes, basePath, subject);
        if (!ar.getActionExitCode().equals(ExitCode.SUCCESS)) {
            throw new WebApplicationException(Response.status(Status.BAD_REQUEST).
                    entity(ar.getCombinedMessage()).build());
        }
    }

    /**
     * Execute a delete AdminCommand with no parameters.
     *
     * @param subject
     * @param command
     * @return
     */
    public ActionReporter executeDeleteCommand(Subject subject, String command) {
        return executeDeleteCommand(subject, command, new ParameterMap());
    }

    /**
     * Execute a delete AdminCommand with the specified parameters.
     *
     * @param subject
     * @param command
     * @param parameters
     * @return
     */
    public ActionReporter executeDeleteCommand(Subject subject, String command, ParameterMap parameters) {
        return executeCommand(subject, command, parameters, Status.BAD_REQUEST, true, true, false);
    }

    /**
     * Execute a delete AdminCommand with the specified parameters.
     *
     * @param subject
     * @param command
     * @param parameters
     * @return
     */
    public ActionReporter executeDeleteCommandManaged(Subject subject, String command, ParameterMap parameters) {
        return executeCommand(subject, command, parameters, Status.BAD_REQUEST, true, true, true);
    }


    /**
     * Execute a writing AdminCommand with no parameters.
     *
     * @param subject
     * @param command
     * @return
     */
    public ActionReporter executeWriteCommand(Subject subject, String command) {
        return executeWriteCommand(subject, command, new ParameterMap());
    }

    /**
     * Execute a writing AdminCommand with the specified parameters.
     *
     * @param subject
     * @param command
     * @param parameters
     * @return
     */
    public ActionReporter executeWriteCommand(Subject subject, String command, ParameterMap parameters) {
        return executeCommand(subject, command, parameters, Status.BAD_REQUEST, true, true, false);
    }

    /**
     * Execute a writing AdminCommand with the specified parameters as managed job.
     *
     * @param subject
     * @param command
     * @param parameters
     * @return
     */
    public ActionReporter executeWriteCommandManaged(Subject subject, String command, ParameterMap parameters) {
        return executeCommand(subject, command, parameters, Status.BAD_REQUEST, true, true, true);
    }

    /**
     * Execute a read-only AdminCommand with the specified parameters.
     *
     * @param subject
     * @param command
     * @return
     */
    public ActionReporter executeReadCommand(Subject subject, String command) {
        return executeReadCommand(subject, command, new ParameterMap());
    }

    /**
     * Execute a read-only AdminCommand with no parameters.
     *
     * @param subject
     * @param command
     * @param parameters
     * @return
     */
    public ActionReporter executeReadCommand(Subject subject, String command, ParameterMap parameters) {
        return executeCommand(subject, command, parameters, Status.NOT_FOUND, true, true, false);
    }

    /**
     * Execute an AdminCommand with the specified parameters.
     *
     * @param command
     * @param parameters
     * @param throwBadRequest (vs. NOT_FOUND)
     * @param throwOnWarning  (vs.ignore warning)
     * @return
     */
    public ActionReporter executeCommand(Subject subject, String command, ParameterMap parameters, Status status, boolean includeFailureMessage, boolean throwOnWarning, boolean managed) {
        RestActionReporter ar = ResourceUtil.runCommand(command, parameters, subject, managed);
        ExitCode code = ar.getActionExitCode();
        if (code.equals(ExitCode.FAILURE) || (code.equals(ExitCode.WARNING) && throwOnWarning)) {
            Throwable t = ar.getFailureCause();
            if (t instanceof SecurityException) {
              throw new WebApplicationException(Response.status(Status.UNAUTHORIZED).build());
            }
            if (includeFailureMessage) {
                throw new WebApplicationException(Response.status(status).entity(ar.getCombinedMessage()).build());
            } else {
                throw new WebApplicationException(status);
            }
        }
        return ar;
    }

    /** Execute an AdminCommand with the specified parameters and
     * return EventOutput suitable for SSE.
     */
    public EventOutput executeSseCommand(Subject subject, String command, ParameterMap parameters) {
        return executeSseCommand(subject, command, parameters, null);
    }

    /** Execute an AdminCommand with the specified parameters and
     * return EventOutput suitable for SSE.
     */
    public EventOutput executeSseCommand(Subject subject, String command, ParameterMap parameters, SseCommandHelper.ActionReportProcessor processor) {
        return ResourceUtil.runCommandWithSse(command, parameters, subject, processor);
    }

    public Locale getLocale(HttpHeaders requestHeaders) {
        return getLocale(requestHeaders.getRequestHeaders());
    }

    public Locale getLocale(MultivaluedMap requestHeaders) {
        String hdr = requestHeaders.getFirst("Accept-Language");
        return (hdr != null) ? new Locale(hdr) : null;
    }

    /*******************************************************************************************************************
     * Private implementation methods
     ******************************************************************************************************************/
    /**
     * Find and return all interfaces that extend baseModel
     *
     * @param baseModel
     * @return
     */
    private Set> getModelExtensions(Class baseModel) {
        Set> exts = new HashSet>();

        if (!extensionsLoaded) {
            synchronized (modelExtensions) {
                if (!extensionsLoaded) {
                    loadModelExtensionMetadata(baseModel);
                }
            }
        }

        List list = modelExtensions.get(baseModel.getName());
        if (list != null) {
            for (String className : list) {
                try {
                    Class c = Class.forName(className, true, baseModel.getClassLoader());
                    exts.add(c);
                } catch (ClassNotFoundException ex) {
                    RestLogging.restLogger.log(Level.SEVERE, null, ex);
                }
            }
        }

        return exts;
    }

    /**
     * Locate and process all RestModelExtension metadata files
     *
     * @param similarClass
     */
    private static void loadModelExtensionMetadata(Class similarClass) {
        try {
            Enumeration urls = similarClass.getClassLoader().getResources("META-INF/restmodelextensions");
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
                    while (reader.ready()) {
                        final String line = reader.readLine();
                        if ((line == null) || line.isEmpty()) {
                            continue;
                        }
                        if (line.charAt(0) != '#') {
                            if (!line.contains(":")) {
                                RestLogging.restLogger.log(Level.INFO,
                                        RestLogging.INCORRECTLY_FORMATTED_ENTRY,
                                        new String[]{
                                                "META-INF/restmodelextensions",
                                                line
                                });
                            }
                            String[] entry = line.split(":");
                            String base = entry[0];
                            String ext = entry[1];
                            List list = modelExtensions.get(base);
                            if (list == null) {
                                list = new ArrayList<>();
                                modelExtensions.put(base, list);
                            }
                            list.add(ext);
                        }
                    }
                }
            }
        } catch (IOException ex) {
            RestLogging.restLogger.log(Level.SEVERE, null, ex);
        }
    }

    private List getSetters(Class clazz) {
        List methods = new ArrayList();

        for (Method method : clazz.getMethods()) {
            if (method.getName().startsWith("set")) {
                methods.add(method);
            }
        }

        return methods;
    }

    private void analyzeInterface(Class iface, Map> properties) throws SecurityException {
        // find class level bean reference
        String defaultBean = null;
        if (iface.isAnnotationPresent(DefaultBeanReference.class)) {
            DefaultBeanReference beanRef = iface.getAnnotation(DefaultBeanReference.class);
            defaultBean = beanRef.bean();   
        }

        for (Method method : iface.getMethods()) {
            String name = method.getName();
            final boolean isGetter = name.startsWith("get");
            if (isGetter || name.startsWith("set")) {
                name = name.substring(3);
                Map property = properties.get(name);
                if (property == null) {
                    property = new HashMap();
                    properties.put(name, property);
                }

                String bean = null;
                String attribute = null;
                AttributeReference ar = method.getAnnotation(AttributeReference.class);
                if (ar != null) {
                    bean = ar.bean();
                    attribute = ar.attribute();
                }
                if (!StringUtil.notEmpty(bean)) {
                    bean = defaultBean;
                }
                if (!StringUtil.notEmpty(attribute)) {
                    attribute = name;
                }
                if (StringUtil.notEmpty(bean) && StringUtil.notEmpty(attribute)) {
                    property.put("annotations", gatherReferencedAttributes(bean, attribute));
                }
                Attribute attr = method.getAnnotation(Attribute.class);
                if (attr != null) {
                    property.put("defaultValue", attr.defaultValue());
                }
                Class type = isGetter
                        ? method.getReturnType()
                        : method.getParameterTypes()[0];
                property.put("type", type);
            }
        }
    }

    private Map> gatherReferencedAttributes(String bean, String attribute) {
        Map> annos = new HashMap>();
        try {
            Class configBeanClass = Class.forName(bean);
            Method m = configBeanClass.getMethod("get" + attribute);
            for (Annotation a : m.getAnnotations()) {
                Map anno = new HashMap();
                for (Method am : a.annotationType().getDeclaredMethods()) {
                    String methodName = am.getName();
                    Object value = am.invoke(a);
                    anno.put(methodName, value);
                }
                annos.put(a.annotationType().getName(), anno);
            }
        } catch (Exception ex) {
            RestLogging.restLogger.log(Level.SEVERE, ex.getLocalizedMessage());
        }

        return annos;
    }

    private void handleGetExtensions(List extensions, Object data) {
        for (RestExtension re : extensions) {
            re.get(data);
        }
    }

    private ParameterMap handlePostExtensions(List extensions, Object data) {
        ParameterMap parameters = new ParameterMap();
        for (RestExtension re : extensions) {
            parameters.mergeAll(re.post(data));
        }
        return parameters;
    }

    /**
     * This builds the representation of a type suitable for use in bytecode. For example, the internal type for String
     * would be "L;java/lang/String;", and a double would be "D".
     *
     * @param type The desired class
     * @return
     */
    private String getInternalTypeString(Class type) {
        return type.isPrimitive()
                ? Primitive.getPrimitive(type.getName()).getInternalType()
                : (type.isArray() ? getInternalName(type.getName()) : ("L" + getInternalName(type.getName() + ";")));
    }

    private String getPropertyName(String name) {
        return name.substring(0, 1).toLowerCase(Locale.getDefault()) + name.substring(1);
    }

    /**
     * This method starts the class definition, adding the JAX-B annotations to allow for marshalling via JAX-RS
     */
    private void visitClass(ClassWriter classWriter, String className, Set> ifaces, Map> properties) {
        String[] ifaceNames = new String[ifaces.size() + 1];
        int i = 1;
        ifaceNames[0] = getInternalName(RestModel.class.getName());
        for (Class iface : ifaces) {
            ifaceNames[i++] = iface.getName().replace(".", "/");
        }
        className = getInternalName(className);
        classWriter.visit(V1_6, ACC_PUBLIC + ACC_SUPER, className,
                null,
                "org/glassfish/admin/rest/composite/RestModelImpl",
                ifaceNames);

        // Add @XmlRootElement
        classWriter.visitAnnotation("Ljakarta/xml/bind/annotation/XmlRootElement;", true).visitEnd();

        // Add @XmlAccessType
        AnnotationVisitor annotation = classWriter.visitAnnotation("Ljakarta/xml/bind/annotation/XmlAccessorType;", true);
        annotation.visitEnum("value", "Ljakarta/xml/bind/annotation/XmlAccessType;", "FIELD");
        annotation.visitEnd();
    }

    /**
     * This method creates the default constructor for the class. Default values are set for any @Attribute defined with
     * a defaultValue.
     *
     */
    private void createConstructor(ClassWriter cw, String className, Map> properties) {
        MethodVisitor method = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
        method.visitCode();
        method.visitVarInsn(ALOAD, 0);
        method.visitMethodInsn(INVOKESPECIAL, "org/glassfish/admin/rest/composite/RestModelImpl", "", "()V");

        for (Map.Entry> property : properties.entrySet()) {
            String fieldName = property.getKey();
            String defaultValue = (String) property.getValue().get("defaultValue");
            if (defaultValue != null && !defaultValue.isEmpty()) {
                setDefaultValue(method, className, fieldName, (Class) property.getValue().get("type"), defaultValue);
            }
        }

        method.visitInsn(RETURN);
        method.visitMaxs(1, 1);
        method.visitEnd();
    }

    /**
     * This method generates the byte code to set the default value for a given field. Efforts are made to determine the
     * best way to create the correct value. If the field is a primitive, the one-arg, String constructor of the
     * appropriate wrapper class is called to generate the value. If the field is not a primitive, a one-arg, String
     * constructor is requested to build the value. If both of these attempts fail, the default value is set using the
     * String representation as given via the @Attribute annotation.
     * 

* TODO: it may make sense to treat primitives here as non-String types. */ private void setDefaultValue(MethodVisitor method, String className, String fieldName, Class fieldClass, String defaultValue) { final String type = getInternalTypeString(fieldClass); Object value = defaultValue; fieldName = getPropertyName(fieldName); if (fieldClass.isPrimitive()) { switch (Primitive.getPrimitive(type)) { case SHORT: value = Short.valueOf(defaultValue); break; case LONG: value = Long.valueOf(defaultValue); break; case INT: value = Integer.valueOf(defaultValue); break; case FLOAT: value = Float.valueOf(defaultValue); break; case DOUBLE: value = Double.valueOf(defaultValue); break; // case CHAR: value = Character.valueOf(defaultValue.charAt(0)); break; case BYTE: value = Byte.valueOf(defaultValue); break; case BOOLEAN: value = Boolean.valueOf(defaultValue); break; } method.visitVarInsn(ALOAD, 0); method.visitLdcInsn(value); method.visitFieldInsn(PUTFIELD, getInternalName(className), fieldName, type); } else { if (!fieldClass.equals(String.class)) { method.visitVarInsn(ALOAD, 0); final String internalName = getInternalName(fieldClass.getName()); method.visitTypeInsn(NEW, internalName); method.visitInsn(DUP); method.visitLdcInsn(defaultValue); method.visitMethodInsn(INVOKESPECIAL, internalName, "", "(Ljava/lang/String;)V"); method.visitFieldInsn(PUTFIELD, getInternalName(className), fieldName, type); } else { method.visitVarInsn(ALOAD, 0); method.visitLdcInsn(value); method.visitFieldInsn(PUTFIELD, getInternalName(className), fieldName, type); } } } /** * Add the field to the class, adding the @XmlAttribute annotation for marshalling purposes. */ private void createField(ClassWriter cw, String name, Class type) { String internalType = getInternalTypeString(type); FieldVisitor field = cw.visitField(ACC_PRIVATE, getPropertyName(name), internalType, null, null); field.visitAnnotation("Ljakarta/xml/bind/annotation/XmlAttribute;", true).visitEnd(); field.visitEnd(); } /** * Create getters and setters for the given field */ private void createGettersAndSetters(ClassWriter cw, Class c, String className, String name, Map props) { Class type = (Class) props.get("type"); String internalType = getInternalTypeString(type); className = getInternalName(className); // Create the getter MethodVisitor getter = cw.visitMethod(ACC_PUBLIC, "get" + name, "()" + internalType, null, null); getter.visitCode(); getter.visitVarInsn(ALOAD, 0); getter.visitFieldInsn(GETFIELD, className, getPropertyName(name), internalType); getter.visitInsn(type.isPrimitive() ? Primitive.getPrimitive(internalType).getReturnOpcode() : ARETURN); getter.visitMaxs(0, 0); getter.visitEnd(); Map> annotations = (Map>) props.get("annotations"); if (annotations != null) { for (Map.Entry> entry : annotations.entrySet()) { String annotationClass = entry.getKey(); Map annotationValues = entry.getValue(); AnnotationVisitor av = getter.visitAnnotation("L" + getInternalName(annotationClass) + ";", true); for (Map.Entry values : annotationValues.entrySet()) { final String paramName = values.getKey(); Object paramValue = values.getValue(); if (Class.class.isAssignableFrom(paramValue.getClass())) { paramValue = org.objectweb.asm.Type.getType("L" + getInternalName(paramValue.getClass().getName()) + ";"); } if (paramValue.getClass().isArray() && (Array.getLength(paramValue) == 0)) { continue; } av.visit(paramName, paramValue); } av.visitEnd(); } } // Create the setter MethodVisitor setter = cw.visitMethod(ACC_PUBLIC, "set" + name, "(" + internalType + ")V", null, null); setter.visitCode(); setter.visitVarInsn(ALOAD, 0); setter.visitVarInsn(type.isPrimitive() ? Primitive.getPrimitive(internalType).getSetOpCode() : ALOAD, 1); setter.visitFieldInsn(PUTFIELD, className, getPropertyName(name), internalType); setter.visitVarInsn(ALOAD, 0); setter.visitLdcInsn(name); setter.visitMethodInsn(INVOKEVIRTUAL, className, "fieldSet", "(Ljava/lang/String;)V"); setter.visitInsn(RETURN); setter.visitMaxs(0, 0); setter.visitEnd(); } /** * Convert the dotted class name to the "internal" (bytecode) representation */ private String getInternalName(String className) { return className.replace(".", "/"); } // TODO: This is duplicated from the generator class. private Class defineClass(Class similarClass, String className, byte[] classBytes) throws Exception { byte[] byteContent = classBytes; ProtectionDomain pd = similarClass.getProtectionDomain(); java.lang.reflect.Method jm = null; for (java.lang.reflect.Method jm2 : ClassLoader.class.getDeclaredMethods()) { if (jm2.getName().equals("defineClass") && jm2.getParameterTypes().length == 5) { jm = jm2; break; } } if (jm == null) {//should never happen, makes findbug happy throw new RuntimeException("cannot find method called defineclass..."); } final java.lang.reflect.Method clM = jm; try { java.security.AccessController.doPrivileged( new java.security.PrivilegedExceptionAction() { @Override public java.lang.Object run() throws Exception { if (!clM.isAccessible()) { clM.setAccessible(true); } return null; } }); RestLogging.restLogger.log(Level.FINEST, "Loading bytecode for {0}", className); final ClassLoader classLoader = similarClass.getClassLoader(); //Thread.currentThread().getContextClassLoader(); // Thread.currentThread().getContextClassLoader(); try { clM.invoke(classLoader, className, byteContent, 0, byteContent.length, pd); } catch (Exception e) { throw new RuntimeException(e); } try { return classLoader.loadClass(className); } catch (ClassNotFoundException cnfEx) { throw new RuntimeException(cnfEx); } } catch (Exception ex) { throw new RuntimeException(ex); } } private static synchronized void initBeanValidator() { if (beanValidator != null) { return; } ClassLoader cl = System.getSecurityManager() == null ? Thread.currentThread().getContextClassLoader() : AccessController.doPrivileged(new PrivilegedAction() { @Override public ClassLoader run() { return Thread.currentThread().getContextClassLoader(); } }); try { Thread.currentThread().setContextClassLoader(Validation.class.getClassLoader()); ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); ValidatorContext validatorContext = validatorFactory.usingContext(); validatorContext.messageInterpolator(new MessageInterpolatorImpl()); beanValidator = validatorContext.getValidator(); } finally { Thread.currentThread().setContextClassLoader(cl); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy