/*
* 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.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;
}
invoke(setter, attribute, model, o);
}
}
}
return model;
}
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 {
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);
}
}
}