Unverified Commit a83b5135 authored by Jay Jay Billings's avatar Jay Jay Billings Committed by GitHub
Browse files

Merge pull request #440 from dbluhm/persistence-fixes-tests

Tests and fixes for Persistence
parents 8add1651 819dca1e
...@@ -53,10 +53,10 @@ public @interface DataField { ...@@ -53,10 +53,10 @@ public @interface DataField {
* Flag whether this field should be searchable in a collection. * Flag whether this field should be searchable in a collection.
* This causes persistence retrieval methods to be generated for this * This causes persistence retrieval methods to be generated for this
* field. * field.
* @return search annotation value * @return searchable annotation value
* @see org.eclipse.ice.dev.annotations.processors.Field#search * @see org.eclipse.ice.dev.annotations.processors.Field#searchable
*/ */
boolean search() default true; boolean searchable() default true;
/** /**
* Flag whether this field can have a value of null. This causes * Flag whether this field can have a value of null. This causes
......
...@@ -174,6 +174,7 @@ public class DataElementProcessor extends AbstractProcessor { ...@@ -174,6 +174,7 @@ public class DataElementProcessor extends AbstractProcessor {
.packageName(element.getPackageName()) .packageName(element.getPackageName())
.elementInterface(element.getName()) .elementInterface(element.getName())
.className(element.getPersistenceHandlerName()) .className(element.getPersistenceHandlerName())
.interfaceName(element.getPersistenceHandlerInterfaceName())
.implementation(element.getImplName()) .implementation(element.getImplName())
.collection(collectionName) .collection(collectionName)
.fields(fields) .fields(fields)
......
...@@ -13,6 +13,7 @@ import javax.lang.model.util.Elements; ...@@ -13,6 +13,7 @@ import javax.lang.model.util.Elements;
import org.eclipse.ice.dev.annotations.DataElement; import org.eclipse.ice.dev.annotations.DataElement;
import org.eclipse.ice.dev.annotations.DataFieldJson; import org.eclipse.ice.dev.annotations.DataFieldJson;
import org.eclipse.ice.dev.annotations.IPersistenceHandler;
import org.eclipse.ice.dev.annotations.Persisted; import org.eclipse.ice.dev.annotations.Persisted;
import lombok.Getter; import lombok.Getter;
...@@ -179,4 +180,15 @@ public class DataElementSpec extends AnnotatedElement { ...@@ -179,4 +180,15 @@ public class DataElementSpec extends AnnotatedElement {
.map(jsons -> Arrays.asList(jsons.value())) .map(jsons -> Arrays.asList(jsons.value()))
.orElse(Collections.emptyList()); .orElse(Collections.emptyList());
} }
/**
* Get the interface name of the persistence handler.
*
* Right now, this is simply the IPersistenceHandler interface. In the future
* this will be a data element specific interface.
* @return Interface name of the Persistence Handler
*/
public String getPersistenceHandlerInterfaceName() {
return IPersistenceHandler.class.getSimpleName();
}
} }
...@@ -138,7 +138,7 @@ public class DataFieldSpec extends AnnotatedElement { ...@@ -138,7 +138,7 @@ public class DataFieldSpec extends AnnotatedElement {
.setter(fieldInfo.setter()) .setter(fieldInfo.setter())
.match(fieldInfo.match()) .match(fieldInfo.match())
.unique(fieldInfo.unique()) .unique(fieldInfo.unique())
.search(fieldInfo.search()) .searchable(fieldInfo.searchable())
.nullable(fieldInfo.nullable()) .nullable(fieldInfo.nullable())
.build(); .build();
} }
......
...@@ -117,7 +117,7 @@ public class DefaultFields { ...@@ -117,7 +117,7 @@ public class DefaultFields {
.docString("The validator used to check the correctness of the data.") .docString("The validator used to check the correctness of the data.")
.nullable(true) .nullable(true)
.defaultField(true) .defaultField(true)
.search(false) .searchable(false)
.build(); .build();
/** /**
......
...@@ -103,7 +103,7 @@ public class Field { ...@@ -103,7 +103,7 @@ public class Field {
/** /**
* Whether this field should be searchable with PersistenceHandler. * Whether this field should be searchable with PersistenceHandler.
*/ */
@Builder.Default boolean search = true; @Builder.Default boolean searchable = true;
/** /**
* Whether this field should return only one from PersistenceHandler. * Whether this field should return only one from PersistenceHandler.
......
...@@ -43,6 +43,11 @@ public class PersistenceHandlerWriter extends VelocitySourceWriter { ...@@ -43,6 +43,11 @@ public class PersistenceHandlerWriter extends VelocitySourceWriter {
*/ */
private static final String CLASS = "class"; private static final String CLASS = "class";
/**
* Context key for interface of PersistenceHandlers
*/
private static final String INTERFACE = "interface";
/** /**
* Context key for collection. * Context key for collection.
*/ */
...@@ -60,14 +65,16 @@ public class PersistenceHandlerWriter extends VelocitySourceWriter { ...@@ -60,14 +65,16 @@ public class PersistenceHandlerWriter extends VelocitySourceWriter {
@Builder @Builder
public PersistenceHandlerWriter( public PersistenceHandlerWriter(
String packageName, String elementInterface, String className, String packageName, String elementInterface, String interfaceName,
String implementation, String collection, @NonNull Fields fields String className, String implementation, String collection,
@NonNull Fields fields
) { ) {
super(); super();
this.template = PERSISTENCE_HANDLER_TEMPLATE; this.template = PERSISTENCE_HANDLER_TEMPLATE;
this.context.put(PACKAGE, packageName); this.context.put(PACKAGE, packageName);
this.context.put(ELEMENT_INTERFACE, elementInterface); this.context.put(ELEMENT_INTERFACE, elementInterface);
this.context.put(CLASS, className); this.context.put(CLASS, className);
this.context.put(INTERFACE, interfaceName);
this.context.put(COLLECTION, collection); this.context.put(COLLECTION, collection);
this.context.put(IMPLEMENTATION, implementation); this.context.put(IMPLEMENTATION, implementation);
this.context.put(FIELDS, fields); this.context.put(FIELDS, fields);
......
## Declarations
#set($interface = "IPersistenceHandler")
##
#if($package) #if($package)
package $package; package $package;
#end #end
import java.util.Map;
import java.util.UUID;
import org.bson.Document; import org.bson.Document;
import org.eclipse.ice.dev.annotations.IDataElement;
import org.eclipse.ice.dev.annotations.IPersistenceHandler; import org.eclipse.ice.dev.annotations.IPersistenceHandler;
import org.eclipse.ice.dev.annotations.PersistenceFilters; import org.eclipse.ice.dev.annotations.PersistenceFilters;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase; import com.mongodb.client.MongoDatabase;
...@@ -46,7 +38,6 @@ public class $class implements $interface<$elementInterface> { ...@@ -46,7 +38,6 @@ public class $class implements $interface<$elementInterface> {
/** /**
* Save the $elementInterface. * Save the $elementInterface.
* @param <T> Object extending IDataElement
* @param element * @param element
* @throws Exception * @throws Exception
*/ */
...@@ -57,7 +48,6 @@ public class $class implements $interface<$elementInterface> { ...@@ -57,7 +48,6 @@ public class $class implements $interface<$elementInterface> {
/** /**
* Find and retrieve all $elementInterface from the collection. * Find and retrieve all $elementInterface from the collection.
* @param <T>
* @return an iterable of the retrieved elements. * @return an iterable of the retrieved elements.
* @throws Exception * @throws Exception
*/ */
...@@ -74,26 +64,26 @@ public class $class implements $interface<$elementInterface> { ...@@ -74,26 +64,26 @@ public class $class implements $interface<$elementInterface> {
*/ */
@Override @Override
public long clear() throws Exception { public long clear() throws Exception {
long count = this.collection.count(); long count = this.collection.countDocuments();
this.collection.drop(); this.collection.drop();
return count; return count;
} }
#macro(findmethod $var $type $docName) #foreach($field in $fields)
#if(${var.Getter} && ${var.Search}) #if(${field.Getter} && ${field.Searchable})
#if(${var.Unique}) #if(${field.Unique})
/** /**
* Find $elementInterface by ${var.Name}. * Find $elementInterface by ${field.Name}.
* @param ${var.VarName} * @param ${field.VarName}
* @return found element or null * @return found element or null
*/ */
#if(${var.DefaultField}) #if(${field.DefaultField})
@Override @Override
#end #end
public $elementInterface findBy${var.NameForMethod}($type ${var.VarName}) throws Exception { public $elementInterface findBy${field.NameForMethod}(#fieldtype ${field.VarName}) throws Exception {
Document doc = this.collection Document doc = this.collection
.find(PersistenceFilters.eq("${docName}", ${var.VarName})) .find(PersistenceFilters.eq("${field.VarName}", ${field.VarName}))
.first(); .first();
if (doc == null) { if (doc == null) {
return null; return null;
...@@ -104,21 +94,18 @@ public class $class implements $interface<$elementInterface> { ...@@ -104,21 +94,18 @@ public class $class implements $interface<$elementInterface> {
#else #else
/** /**
* Find $elementInterface by ${var.Name}. * Find $elementInterface by ${field.Name}.
* @param ${var.VarName} * @param ${field.VarName}
* @return Iterator of results * @return Iterator of results
*/ */
#if(${var.DefaultField}) #if(${field.DefaultField})
@Override @Override
#end #end
public Iterable<$elementInterface> findBy${var.NameForMethod}(${var.Type} ${var.VarName}) throws Exception { public Iterable<$elementInterface> findBy${field.NameForMethod}(#fieldtype ${field.VarName}) throws Exception {
return this.collection.find(PersistenceFilters.eq("${var.VarName}", ${var.VarName})) return this.collection.find(PersistenceFilters.eq("${field.VarName}", ${field.VarName}))
.map(doc -> mapper.convertValue(doc, ${implementation}.class)); .map(doc -> mapper.convertValue(doc, ${implementation}.class));
} }
#end## if unique #end## if unique
#end## if getter and search #end## if getter and search
#end## macro findmethod #end## foreach
#foreach($field in $fields)
#findmethod($field ${field.Type} ${field.VarName})
#end
} }
\ No newline at end of file
/*******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Daniel Bluhm - Initial implementation
*******************************************************************************/
package org.eclipse.ice.tests.dev.annotations.processors;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import javax.annotation.processing.Processor;
import javax.tools.JavaFileObject;
import org.eclipse.ice.dev.annotations.processors.DataElementProcessor;
import com.google.testing.compile.Compilation;
import static com.google.testing.compile.Compiler.*;
/**
* Helper class for testing DataElement related annotations.
* @author Daniel Bluhm
*/
public class DataElementAnnotationTestHelper {
/**
* Retrieve an instance of Lombok's Annotation Processor.
*
* This is a nasty method that violates the accessibility of the Processor by
* reflection but is necessary to correctly process and test the generated code.
* @return lombok annotation processor
*/
private Processor getLombokAnnotationProcessor() {
Processor p = null;
try {
Class<?> c = Class.forName("lombok.launch.AnnotationProcessorHider$AnnotationProcessor");
Constructor<?> constructor = c.getConstructor();
constructor.setAccessible(true);
p = (Processor) constructor.newInstance();
} catch (
ClassNotFoundException | InstantiationException |
IllegalAccessException | IllegalArgumentException |
InvocationTargetException | NoSuchMethodException |
SecurityException e
) {
System.err.println("Failed to get Lombok AnnotationProcessor!");
e.printStackTrace();
}
return p;
}
/**
* Compile the sources with needed processors.
* @param sources to compile
* @return Compilation result
*/
public Compilation compile(JavaFileObject... sources) {
return javac()
.withProcessors(
getLombokAnnotationProcessor(),
new DataElementProcessor()
).compile(sources);
}
}
package org.eclipse.ice.tests.dev.annotations.processors; /*******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
import static org.junit.jupiter.api.Assertions.fail; * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Daniel Bluhm - Initial implementation
*******************************************************************************/
import java.lang.reflect.Constructor; package org.eclipse.ice.tests.dev.annotations.processors;
import java.lang.reflect.InvocationTargetException;
import static com.google.testing.compile.Compiler.*;
import static com.google.testing.compile.CompilationSubject.*; import static com.google.testing.compile.CompilationSubject.*;
import javax.annotation.processing.Processor;
import javax.tools.JavaFileObject; import javax.tools.JavaFileObject;
import org.eclipse.ice.dev.annotations.processors.DataElementProcessor;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import com.google.testing.compile.Compilation; import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
...@@ -38,6 +40,12 @@ import lombok.AllArgsConstructor; ...@@ -38,6 +40,12 @@ import lombok.AllArgsConstructor;
*/ */
class DataElementProcessorTest { class DataElementProcessorTest {
/**
* Helper for testing DataElement related annotations.
*/
private static DataElementAnnotationTestHelper helper =
new DataElementAnnotationTestHelper();
/** /**
* Fully qualified name of the generated interface. * Fully qualified name of the generated interface.
*/ */
...@@ -53,7 +61,7 @@ class DataElementProcessorTest { ...@@ -53,7 +61,7 @@ class DataElementProcessorTest {
* @author Daniel Bluhm * @author Daniel Bluhm
*/ */
@AllArgsConstructor @AllArgsConstructor
private static enum Inputs { private static enum Inputs implements JavaFileObjectResource{
HELLO_WORLD("HelloWorld.java"), HELLO_WORLD("HelloWorld.java"),
NAME_MISSING("DataElementNameMissing.java"), NAME_MISSING("DataElementNameMissing.java"),
ON_ENUM("DataElementOnEnum.java"), ON_ENUM("DataElementOnEnum.java"),
...@@ -75,19 +83,16 @@ class DataElementProcessorTest { ...@@ -75,19 +83,16 @@ class DataElementProcessorTest {
/** /**
* Parent directory of inputs. Prepended to all paths. * Parent directory of inputs. Prepended to all paths.
*/ */
private static final String PARENT = "input/"; private static final String PARENT = "input/DataElement/";
/** /**
* Path to inputs. * Path to inputs.
*/ */
private String path; private String filename;
/** @Override
* Retrieve the JavaFileObject corresponding to this input. public String getPath() {
* @return input as a JavaFileObject return PARENT + this.filename;
*/
public JavaFileObject get() {
return JavaFileObjects.forResource(PARENT + this.path);
} }
} }
...@@ -96,7 +101,7 @@ class DataElementProcessorTest { ...@@ -96,7 +101,7 @@ class DataElementProcessorTest {
* @author Daniel Bluhm * @author Daniel Bluhm
*/ */
@AllArgsConstructor @AllArgsConstructor
private static enum Patterns { private static enum Patterns implements JavaFileObjectResource {
DEFAULTS_INT("Defaults.java"), DEFAULTS_INT("Defaults.java"),
DEFAULTS_IMPL("DefaultsImplementation.java"), DEFAULTS_IMPL("DefaultsImplementation.java"),
SINGLE_INT("Single.java"), SINGLE_INT("Single.java"),
...@@ -116,59 +121,17 @@ class DataElementProcessorTest { ...@@ -116,59 +121,17 @@ class DataElementProcessorTest {
/** /**
* Parent directory of inputs. Prepended to all paths. * Parent directory of inputs. Prepended to all paths.
*/ */
private static final String PARENT = "patterns/"; private static final String PARENT = "patterns/DataElement/";
/** /**
* Path to inputs. * Path to inputs.
*/ */
private String path; private String filename;
/**
* Retrieve the JavaFileObject corresponding to this pattern.
* @return input as a JavaFileObject
*/
public JavaFileObject get() {
return JavaFileObjects.forResource(PARENT + this.path);
}
}
/** @Override
* Retrieve an instance of Lombok's Annotation Processor. public String getPath() {
* return PARENT + this.filename;
* This is a nasty method that violates the accessibility of the Processor by
* reflection but is necessary to correctly process and test the generated code.
* @return lombok annotation processor
*/
private static Processor getLombokAnnotationProcessor() {
Processor p = null;
try {
Class<?> c = Class.forName("lombok.launch.AnnotationProcessorHider$AnnotationProcessor");
Constructor<?> constructor = c.getConstructor();
constructor.setAccessible(true);
p = (Processor) constructor.newInstance();
} catch (
ClassNotFoundException | InstantiationException |
IllegalAccessException | IllegalArgumentException |
InvocationTargetException | NoSuchMethodException |
SecurityException e
) {
System.err.println("Failed to get Lombok AnnotationProcessor!");
e.printStackTrace();
}
return p;
} }
/**
* Compile the sources with needed processors.
* @param sources to compile
* @return Compilation result
*/