Package org.glassfish.admin.rest.composite

Source Code of org.glassfish.admin.rest.composite.CompositeUtil$LazyHolder

/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012 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.
*/
package org.glassfish.admin.rest.composite;

import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.v3.common.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 java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorContext;
import javax.validation.ValidatorFactory;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.glassfish.admin.rest.RestExtension;
import org.glassfish.admin.rest.composite.metadata.AttributeReference;
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.Util;
import org.glassfish.admin.rest.utils.xml.RestActionReporter;
import org.glassfish.api.ActionReport.ExitCode;
import org.glassfish.api.admin.ParameterMap;
import org.glassfish.internal.api.Globals;
import org.glassfish.jersey.media.sse.EventChannel;
import static org.glassfish.pfl.objectweb.asm.Opcodes.*;
import org.jvnet.hk2.config.Attribute;
import org.jvnet.hk2.config.MessageInterpolatorImpl;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;

/**
* @author jdlee
*/
public class CompositeUtil {
    private static final Map<String, Class<?>> generatedClasses = new HashMap<String, Class<?>>();
    private static final Map<String, List<String>> modelExtensions = new HashMap<String, List<String>>();
    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> T getModel(Class<T> modelIface) {
        String className = modelIface.getName() + "Impl";
        if (!generatedClasses.containsKey(className)) {
            Map<String, Map<String, Object>> properties = new HashMap<String, Map<String, Object>>();

            Set<Class<?>> 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<String, Map<String, Object>> entry : properties.entrySet()) {
                String name = entry.getKey();
                final Map<String, Object> 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);
        }
    }

    /**
     * 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<RestExtension> extensions = new ArrayList<RestExtension>();

        for (RestExtension extension : Globals.getDefaultHabitat().<RestExtension>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) {
        String name;
        Map<String, String> currentValues = Util.getCurrentValues(basePath, Globals.getDefaultHabitat());
        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) {
                        Logger.getLogger(CompositeUtil.class.getName()).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) {
                    Logger.getLogger(CompositeUtil.class.getName()).log(Level.FINE, null, ex);
                }
            }
        }

        return parameters;
    }

    /**
     * Convert the given <code>RestModel</code> encoded as JSON to a live Java Object.
     *
     * @param modelClass The target <code>RestModel</code> type
     * @param json       The json encoding of the object
     * @return
     */
    public <T> T unmarshallClass(Class<T> 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];
            if (json.has(attribute)) {
                java.lang.Object o = json.get(attribute);
                if (JSONArray.class.isAssignableFrom(o.getClass())) {
                    Object values = processJsonArray(param0, (JSONArray) o);
                    invoke(setter, attribute, model, values);
                } else if (JSONObject.class.isAssignableFrom(o.getClass())) {
                    invoke(setter, attribute, model, unmarshallClass(param0.getClass(), (JSONObject) o));
                } else {
                    if ("null".equals(o.toString())) {
                        o = null;
                    }
                    if (!isUnmodifiedConfidentialProperty(modelClass, name, o)) {
                        invoke(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);
    }

    private Object processJsonArray(Type param0, JSONArray array) throws JSONException {
//                    List values = new ArrayList();
        Type type = null;
        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<T> or T[]. While this works, perhaps we should only support List<T>. It's cleaner.
        Object values = isArray ?
                Array.newInstance((Class<?>) type, array.length()) :
                new ArrayList();

        for (int i = 0; i < array.length(); i++) {
            Object element = array.get(i);
            if (JSONObject.class.isAssignableFrom(element.getClass())) {
                if (isArray) {
                    Array.set(values, i, unmarshallClass((Class) type, (JSONObject) element));
                } else {
                    ((List)values).add(unmarshallClass((Class) type, (JSONObject) element));
                }
            } else {
                if (isArray) {
                    Array.set(values, i, element);
                } else {
                    ((List)values).add(element);
                }
            }
        }
        return values;
    }

    private void invoke(Method m, String attribute, Object o, Object... args) {
        try {
            m.invoke(o, args);
        } catch (IllegalArgumentException iae) {
            throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(iae.getLocalizedMessage()).build());
        } catch (Exception e) {
            throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getLocalizedMessage()).build());
        }
    }

    /**
     * If the <code>HelpText</code> annotation is in the list of <code>Annotation</code>s, 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 <T> Set<ConstraintViolation<T>> validateRestModel(T model) {
        initBeanValidator();

        Set<ConstraintViolation<T>> constraintViolations = beanValidator.validate(model);
        if (constraintViolations == null || constraintViolations.isEmpty()) {
            return Collections.EMPTY_SET;
        }

        return constraintViolations;
    }

    public <T> String getValidationFailureMessages(Set<ConstraintViolation<T>> 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<String, String> changes, String basePath) {
        RestActionReporter ar = Util.applyChanges(changes, basePath);
        if (!ar.getActionExitCode().equals(ExitCode.SUCCESS)) {
            throw new WebApplicationException(Response.status(Status.BAD_REQUEST).
                    entity(ar.getCombinedMessage()).build());
        }
    }

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

    /**
     * Execute a delete <code>AdminCommand</code> 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, false, true);
    }

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

    /**
     * Execute a writing <code>AdminCommand</code> 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, true, true);
    }

    /**
     * Execute a read-only <code>AdminCommand</code> 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 <code>AdminCommand</code> with no parameters.
     *
     * @param subject
     * @param command
     * @param parameters
     * @return
     */
    public ActionReporter executeReadCommand(Subject subject, String command, ParameterMap parameters) {
        return executeCommand(subject, command, parameters, false, true);
    }

    /**
     * Execute an <code>AdminCommand</code> 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, boolean throwBadRequest, boolean throwOnWarning) {
        RestActionReporter ar = ResourceUtil.runCommand(command, parameters,
                Globals.getDefaultHabitat(), "", subject); //TODO The last parameter is resultType and is not used. Refactor the called method to remove it
        ExitCode code = ar.getActionExitCode();
        if (code.equals(ExitCode.FAILURE) || (code.equals(ExitCode.WARNING) && throwOnWarning)) {
            if (throwBadRequest) {
                throw new WebApplicationException(Response.status(Status.BAD_REQUEST)
                        .entity(ar.getCombinedMessage())
                        .build());
            } else {
                // TODO: Why NOT_FOUND?
                throw new WebApplicationException(Status.NOT_FOUND);
            }
        }
        return ar;
    }
   
    /** Execute an <code>AdminCommand</code> with the specified parameters and
     * return EventChannel suitable for SSE.
     */
    public EventChannel executeSseCommand(Subject subject, String command, ParameterMap parameters) {
        return executeSseCommand(subject, command, parameters, null);
    }
   
    /** Execute an <code>AdminCommand</code> with the specified parameters and
     * return EventChannel suitable for SSE.
     */
    public EventChannel executeSseCommand(Subject subject, String command, ParameterMap parameters, SseCommandHelper.ActionReportProcessor processor) {
        return ResourceUtil.runCommandWithSse(command, parameters, subject, processor);
    }

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

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

        List<String> 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) {
                    Logger.getLogger(CompositeUtil.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        return exts;
    }

    /**
     * Locate and process all <code>RestModelExtension</code> metadata files
     *
     * @param similarClass
     */
    private void loadModelExtensionMetadata(Class<?> similarClass) {
        BufferedReader reader = null;
        try {
            Enumeration<URL> urls = similarClass.getClassLoader().getResources("META-INF/restmodelextensions");
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                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(":")) {
                            Logger.getLogger(CompositeUtil.class.getName()).log(Level.INFO,
                                    "Incorrectly formatted entry in {0}: {1}",
                                    new String[]{"META-INF/restmodelextensions", line}); // TODO: i18n
                        }
                        String[] entry = line.split(":");
                        String base = entry[0];
                        String ext = entry[1];
                        List<String> list = modelExtensions.get(base);
                        if (list == null) {
                            list = new ArrayList<String>();
                            modelExtensions.put(base, list);
                        }
                        list.add(ext);
                    }
                }

            }
        } catch (IOException ex) {
            Logger.getLogger(CompositeUtil.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ex) {
                    Logger.getLogger(CompositeUtil.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    private List<Method> getSetters(Class<?> clazz) {
        List<Method> methods = new ArrayList<Method>();

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

        return methods;
    }

    private void analyzeInterface(Class<?> iface, Map<String, Map<String, Object>> properties) throws SecurityException {
        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<String, Object> property = properties.get(name);
                if (property == null) {
                    property = new HashMap<String, Object>();
                    properties.put(name, property);
                }

                AttributeReference ar = method.getAnnotation(AttributeReference.class);
                if (ar != null) {
                    property.put("annotations", gatherReferencedAttributes((AttributeReference) ar));
                }
                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<String, Map<String, Object>> gatherReferencedAttributes(AttributeReference ar) {
        Map<String, Map<String, Object>> annos = new HashMap<String, Map<String, Object>>();
        try {
            Class<?> configBeanClass = Class.forName(ar.configBean());
            Method m = configBeanClass.getMethod(ar.methodName());
            for (Annotation a : m.getAnnotations()) {
                Map<String, Object> anno = new HashMap<String, Object>();
                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) {
            Logger.getLogger(CompositeUtil.class.getName()).log(Level.SEVERE, ex.getLocalizedMessage());
        }

        return annos;
    }

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

    private ParameterMap handlePostExtensions(List<RestExtension> 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<Class<?>> ifaces, Map<String, Map<String, Object>> 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("Ljavax/xml/bind/annotation/XmlRootElement;", true).visitEnd();

        // Add @XmlAccessType
        AnnotationVisitor annotation = classWriter.visitAnnotation("Ljavax/xml/bind/annotation/XmlAccessorType;", true);
        annotation.visitEnum("value", "Ljavax/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<String, Map<String, Object>> properties) {
        MethodVisitor method = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        method.visitCode();
        method.visitVarInsn(ALOAD, 0);
        method.visitMethodInsn(INVOKESPECIAL, "org/glassfish/admin/rest/composite/RestModelImpl", "<init>", "()V");

        for (Map.Entry<String, Map<String, Object>> 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.
     * <p/>
     * 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, "<init>", "(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("Ljavax/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<String, Object> 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<String, Map<String, Object>> annotations = (Map<String, Map<String, Object>>) props.get("annotations");
        if (annotations != null) {
            for (Map.Entry<String, Map<String, Object>> entry : annotations.entrySet()) {
                String annotationClass = entry.getKey();
                Map<String, Object> annotationValues = entry.getValue();
                AnnotationVisitor av = getter.visitAnnotation("L" + getInternalName(annotationClass) + ";", true);
                for (Map.Entry<String, Object> 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;
                        }
                    });

            Logger.getLogger(CompositeUtil.class.getName()).log(Level.FINE, "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<ClassLoader>() {
            @Override
            public ClassLoader run() {
                return Thread.currentThread().getContextClassLoader();
            }
        });
        try {
            Thread.currentThread().setContextClassLoader(null);
            ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
            ValidatorContext validatorContext = validatorFactory.usingContext();
            validatorContext.messageInterpolator(new MessageInterpolatorImpl());
            beanValidator = validatorContext.getValidator();
        } finally {
            Thread.currentThread().setContextClassLoader(cl);
        }
    }

}
TOP

Related Classes of org.glassfish.admin.rest.composite.CompositeUtil$LazyHolder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.