Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Apr 12, 2020
1 parent 869f65e commit c5309d7
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMember;
import javassist.CtMethod;
import javassist.CtPrimitiveType;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Bytecode;
Expand All @@ -81,6 +83,8 @@
import javassist.bytecode.SignatureAttribute;
import javassist.bytecode.StackMap;
import javassist.bytecode.StackMapTable;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.EnumMemberValue;
import javassist.compiler.CompileError;
import javassist.compiler.JvstCodeGen;
import javassist.compiler.Lex;
Expand Down Expand Up @@ -385,6 +389,13 @@ private <T> Class<? extends T> createProxyClass(EntityViewManager entityViewMana
CtClass cc = pool.makeClass(proxyClassName);
CtClass superCc;

// ConstPool constPool = cc.getClassFile().getConstPool();
// AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
// Annotation annotation = new Annotation("javax.xml.bind.annotation.XmlAccessorType", constPool);
// annotation.addMemberValue("value", new EnumMemberValue(constPool.addUtf8Info("javax.xml.bind.annotation.XmlAccessType"), constPool.addUtf8Info("PROPERTY"), constPool));
// annotationsAttribute.addAnnotation(annotation);
// cc.getClassFile().addAttribute(annotationsAttribute);

ClassPath classPath = new ClassClassPath(clazz);
pool.insertClassPath(classPath);

Expand Down Expand Up @@ -423,6 +434,7 @@ private <T> Class<? extends T> createProxyClass(EntityViewManager entityViewMana
cc.addInterface(pool.get(DirtyStateTrackable.class.getName()));
initialStateField = new CtField(pool.get(Object[].class.getName()), "$$_initialState", cc);
initialStateField.setModifiers(getModifiers(false));
annotateInternalField(initialStateField);
cc.addField(initialStateField);

addGetter(cc, initialStateField, "$$_getInitialState");
Expand All @@ -432,6 +444,7 @@ private <T> Class<? extends T> createProxyClass(EntityViewManager entityViewMana
cc.addInterface(pool.get(DirtyTracker.class.getName()));
mutableStateField = new CtField(pool.get(Object[].class.getName()), "$$_mutableState", cc);
mutableStateField.setModifiers(getModifiers(false));
annotateInternalField(mutableStateField);
cc.addField(mutableStateField);

CtField initializedField = new CtField(CtClass.booleanType, "$$_initialized", cc);
Expand All @@ -441,12 +454,15 @@ private <T> Class<? extends T> createProxyClass(EntityViewManager entityViewMana
readOnlyParentsField = new CtField(pool.get(List.class.getName()), "$$_readOnlyParents", cc);
readOnlyParentsField.setModifiers(getModifiers(true));
readOnlyParentsField.setGenericSignature(Descriptor.of(List.class.getName()) + "<" + Descriptor.of(Object.class.getName()) + ">;");
annotateInternalField(readOnlyParentsField);
cc.addField(readOnlyParentsField);
parentField = new CtField(pool.get(DirtyTracker.class.getName()), "$$_parent", cc);
parentField.setModifiers(getModifiers(true));
annotateInternalField(parentField);
cc.addField(parentField);
parentIndexField = new CtField(CtClass.intType, "$$_parentIndex", cc);
parentIndexField.setModifiers(getModifiers(true));
annotateInternalField(parentIndexField);
cc.addField(parentIndexField);

dirtyChecking = true;
Expand Down Expand Up @@ -479,6 +495,8 @@ private <T> Class<? extends T> createProxyClass(EntityViewManager entityViewMana
idAttribute = (AbstractMethodAttribute<? super T, ?>) viewType.getIdAttribute();
versionAttribute = (AbstractMethodAttribute<? super T, ?>) viewType.getVersionAttribute();
idField = addMembersForAttribute(idAttribute, clazz, cc, null, false, true, mutableStateField != null);
// TODO: Copy JAXB annotations from getter and setter?
annotateField(idField, "javax.xml.bind.annotation.XmlElement");
fieldMap.put(idAttribute.getName(), idField);
attributeFields[0] = idField;
attributeTypes[0] = idField.getType();
Expand Down Expand Up @@ -533,6 +551,7 @@ private <T> Class<? extends T> createProxyClass(EntityViewManager entityViewMana
} else {
dirtyField = new CtField(CtClass.longType, "$$_dirty", cc);
dirtyField.setModifiers(getModifiers(true));
annotateInternalField(dirtyField);
cc.addField(dirtyField);

boolean allSupportDirtyTracking = true;
Expand Down Expand Up @@ -572,6 +591,7 @@ private <T> Class<? extends T> createProxyClass(EntityViewManager entityViewMana

if (hasEmptyConstructor) {
// Create constructor for create models
cc.addConstructor(createEmptyConstructor(cc));
cc.addConstructor(createCreateConstructor(entityViewManager, managedViewType, cc, attributeFields, attributeTypes, idField, initialStateField, mutableStateField, methodAttributes, mutableAttributeCount, alwaysDirtyMask, unsafe));
}

Expand Down Expand Up @@ -617,6 +637,13 @@ private <T> Class<? extends T> createProxyClass(EntityViewManager entityViewMana
}
}

private CtConstructor createEmptyConstructor(CtClass cc) throws CannotCompileException {
CtConstructor constructor = new CtConstructor(new CtClass[0], cc);
constructor.setModifiers(Modifier.PUBLIC);
constructor.setBody("this((" + cc.getName() + ") null, " + cc.getName() + "#" + SerializableEntityViewManager.EVM_FIELD_NAME + ".getOptionalParameters());");
return constructor;
}

private void createSerializationSubclass(ManagedViewTypeImplementor<?> managedViewType, CtClass cc) throws Exception {
boolean hasSelfConstructor = false;
OUTER: for (MappingConstructor<?> constructor : managedViewType.getConstructors()) {
Expand Down Expand Up @@ -1148,6 +1175,7 @@ private void addIsNewMembers(ManagedViewType<?> managedViewType, CtClass cc, Cla
if (managedViewType.isCreatable()) {
CtField isNewField = new CtField(CtClass.booleanType, "$$_isNew", cc);
isNewField.setModifiers(getModifiers(true));
annotateInternalField(isNewField);
cc.addField(isNewField);

addGetter(cc, isNewField, "$$_isNew");
Expand All @@ -1162,6 +1190,21 @@ private void addIsNewMembers(ManagedViewType<?> managedViewType, CtClass cc, Cla
}
}
}

private void annotateInternalField(CtMember member) {
annotateField(member, "javax.xml.bind.annotation.XmlTransient");
}

private void annotateField(CtMember member, String fqn) {
// ConstPool constPool = member.getDeclaringClass().getClassFile().getConstPool();
// AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
// annotationsAttribute.addAnnotation(new Annotation(fqn, constPool));
// if (member instanceof CtField) {
// ((CtField) member).getFieldInfo().addAttribute(annotationsAttribute);
// } else {
// ((CtMethod) member).getMethodInfo().addAttribute(annotationsAttribute);
// }
}

private CtMethod addGetter(CtClass cc, CtField field, String methodName) throws CannotCompileException {
return addGetter(cc, field, methodName, field.getFieldInfo().getDescriptor(), false);
Expand Down Expand Up @@ -1240,6 +1283,9 @@ private void createGettersAndSetters(AbstractMethodAttribute<?, ?> attribute, Cl
List<Method> bridgeGetters = getBridgeGetters(clazz, attribute, getter);

CtMethod attributeGetter = addGetter(cc, attributeField, getter.getName());
if (isId) {
annotateInternalField(attributeGetter);
}

if (genericSignature != null) {
String getterGenericSignature = "()" + genericSignature;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,7 @@ private static void printConstructors(StringBuilder sb, MetaEntityView entity, C
boolean postLoadReflection = preparePostLoad(sb, entity, context);
if (entity.hasEmptyConstructor()) {
if (entity.getMembers().size() > 0) {
printEmptyConstructor(sb, entity, context);
printCreateConstructor(sb, entity, context);
sb.append(NEW_LINE);
}
Expand Down Expand Up @@ -1760,6 +1761,12 @@ private static void printTupleAssignmentConstructor(StringBuilder sb, MetaConstr
sb.append(NEW_LINE);
}

private static void printEmptyConstructor(StringBuilder sb, MetaEntityView entity, Context context) {
sb.append(" public ").append(entity.getSimpleName()).append(IMPL_CLASS_NAME_SUFFIX).append("() {").append(NEW_LINE);
sb.append(" super((").append(entity.getSimpleName()).append(IMPL_CLASS_NAME_SUFFIX).append(") null, ").append(EVM_FIELD_NAME).append(".getOptionalParameters());").append(NEW_LINE);
sb.append(" }").append(NEW_LINE);
}

private static void printCreateConstructor(StringBuilder sb, MetaEntityView entity, Context context) {
boolean postCreateReflection = false;
if (entity.getPostCreate() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ public class CatRestController {
@Autowired
private CatViewRepository catViewRepository;

@RequestMapping(path = "/cats", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(path = "/cats", method = RequestMethod.POST, consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<String> createCat(@RequestBody CatCreateView catCreateView) {
catViewRepository.save(catCreateView);

return ResponseEntity.ok(catCreateView.getId().toString());
}

@RequestMapping(path = "/cats/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(path = "/cats/{id}", method = RequestMethod.PUT, consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<String> updateCat(@PathVariable("id") long id, @RequestBody CatUpdateView catUpdateView) {
catViewRepository.save(catUpdateView);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@
import com.blazebit.persistence.view.CreatableEntityView;
import com.blazebit.persistence.view.EntityView;

import javax.xml.bind.annotation.XmlRootElement;

/**
* @author Christian Beikov
* @since 1.4.0
*/
@CreatableEntityView
@EntityView(Cat.class)
@XmlRootElement(name = "cat")
public interface CatCreateView extends CatUpdateView {

PersonIdView getOwner();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@
import com.blazebit.persistence.view.EntityView;
import com.blazebit.persistence.view.UpdatableEntityView;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

/**
* @author Christian Beikov
* @since 1.4.0
*/
@UpdatableEntityView
@EntityView(Cat.class)
@XmlRootElement(name = "cat")
public interface CatUpdateView extends CatSimpleView {

void setName(String name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@

import com.blazebit.persistence.integration.jackson.EntityViewIdValueAccessor;
import com.blazebit.persistence.spring.data.webmvc.KeysetPageableArgumentResolver;
import com.blazebit.persistence.spring.data.webmvc.impl.json.EntityViewAwareMappingJackson2HttpMessageConverter;
import com.blazebit.persistence.spring.data.webmvc.impl.json.EntityViewIdHandlerInterceptor;
import com.blazebit.persistence.spring.data.webmvc.impl.json.EntityViewIdValueHolder;
import com.blazebit.persistence.view.EntityViewManager;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -73,6 +70,7 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentRes
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// Add it to the beginning so it has precedence over the builtin
converters.add(0, new EntityViewAwareMappingJackson2HttpMessageConverter(entityViewManager, idAttributeAccessor()));
converters.add(1, new EntityViewAwareJaxb2RootElementHttpMessageConverter(entityViewManager));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright 2014 - 2019 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.blazebit.persistence.spring.data.webmvc.impl;

import com.blazebit.persistence.view.EntityViewManager;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.util.Assert;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.transform.Source;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
* @author Christian Beikov
* @since 1.5.0
*/
public class EntityViewAwareJaxb2RootElementHttpMessageConverter extends Jaxb2RootElementHttpMessageConverter {

private final ConcurrentMap<Class<?>, JAXBContext> jaxbContexts = new ConcurrentHashMap<Class<?>, JAXBContext>(64);
private final EntityViewManager entityViewManager;

public EntityViewAwareJaxb2RootElementHttpMessageConverter(EntityViewManager entityViewManager) {
this.entityViewManager = entityViewManager;
}

@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
if (entityViewManager.getMetamodel().view(clazz) == null) {
return false;
}
return super.canRead(clazz, mediaType);
}

@Override
protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws IOException {
try {
Class<?> implementationClass = entityViewManager.getReference(clazz, null).getClass();
source = processSource(source);
// TODO: Need the serialization class here instead
Unmarshaller unmarshaller = createUnmarshallerInternal(implementationClass);
if (clazz.isAnnotationPresent(XmlRootElement.class)) {
return unmarshaller.unmarshal(source);
}
else {
JAXBElement<?> jaxbElement = unmarshaller.unmarshal(source, implementationClass);
return jaxbElement.getValue();
}
}
catch (NullPointerException ex) {
if (!isSupportDtd()) {
throw new HttpMessageNotReadableException("NPE while unmarshalling. " +
"This can happen on JDK 1.6 due to the presence of DTD " +
"declarations, which are disabled.", ex);
}
throw ex;
}
catch (UnmarshalException ex) {
throw new HttpMessageNotReadableException("Could not unmarshal to [" + clazz + "]: " + ex.getMessage(), ex);

}
catch (JAXBException ex) {
throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
}
}

protected Marshaller createMarshallerInternal(Class<?> clazz) {
try {
JAXBContext jaxbContext = getJaxbContextInternal(clazz);
Marshaller marshaller = jaxbContext.createMarshaller();
customizeMarshaller(marshaller);
return marshaller;
}
catch (JAXBException ex) {
throw new HttpMessageConversionException(
"Could not create Marshaller for class [" + clazz + "]: " + ex.getMessage(), ex);
}
}

protected Unmarshaller createUnmarshallerInternal(Class<?> clazz) throws JAXBException {
try {
JAXBContext jaxbContext = getJaxbContextInternal(clazz);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
// Register adapters
try {
unmarshaller.setAdapter((XmlAdapter) Class.forName("com.blazebit.persistence.examples.spring.data.webmvc.view.CatAdapter").newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
customizeUnmarshaller(unmarshaller);
return unmarshaller;
}
catch (JAXBException ex) {
throw new HttpMessageConversionException(
"Could not create Unmarshaller for class [" + clazz + "]: " + ex.getMessage(), ex);
}
}

protected JAXBContext getJaxbContextInternal(Class<?> clazz) {
Assert.notNull(clazz, "'clazz' must not be null");
JAXBContext jaxbContext = this.jaxbContexts.get(clazz);
if (jaxbContext == null) {
try {
// Map<Class<?>, Class<?>> map = new HashMap<>();
// map.put(DirtyTracker.class, Object.class);
// jaxbContext = JAXBContext.newInstance(new Class[]{ clazz }, Collections.singletonMap("com.sun.xml.bind.subclassReplacements", map));
jaxbContext = JAXBContext.newInstance(new Class[]{ clazz });
this.jaxbContexts.putIfAbsent(clazz, jaxbContext);
}
catch (JAXBException ex) {
throw new HttpMessageConversionException(
"Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex);
}
}
return jaxbContext;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.blazebit.persistence.spring.data.webmvc.impl.json;
package com.blazebit.persistence.spring.data.webmvc.impl;

import com.blazebit.persistence.integration.jackson.EntityViewAwareObjectMapper;
import com.blazebit.persistence.integration.jackson.EntityViewIdValueAccessor;
Expand Down
Loading

0 comments on commit c5309d7

Please sign in to comment.