org.glassfish.admin.rest.composite.CompositeUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* 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 Annotation
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 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 interface
s 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);
}
}
}