GitLab will be shut down on June 25 to move to a new facility. https://www.eclipsestatus.io/incidents/5ffy27gwcbx7

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 {
* Flag whether this field should be searchable in a collection.
* This causes persistence retrieval methods to be generated for this
* field.
* @return search annotation value
* @see org.eclipse.ice.dev.annotations.processors.Field#search
* @return searchable annotation value
* @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
......
......@@ -174,6 +174,7 @@ public class DataElementProcessor extends AbstractProcessor {
.packageName(element.getPackageName())
.elementInterface(element.getName())
.className(element.getPersistenceHandlerName())
.interfaceName(element.getPersistenceHandlerInterfaceName())
.implementation(element.getImplName())
.collection(collectionName)
.fields(fields)
......
......@@ -13,6 +13,7 @@ import javax.lang.model.util.Elements;
import org.eclipse.ice.dev.annotations.DataElement;
import org.eclipse.ice.dev.annotations.DataFieldJson;
import org.eclipse.ice.dev.annotations.IPersistenceHandler;
import org.eclipse.ice.dev.annotations.Persisted;
import lombok.Getter;
......@@ -179,4 +180,15 @@ public class DataElementSpec extends AnnotatedElement {
.map(jsons -> Arrays.asList(jsons.value()))
.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 {
.setter(fieldInfo.setter())
.match(fieldInfo.match())
.unique(fieldInfo.unique())
.search(fieldInfo.search())
.searchable(fieldInfo.searchable())
.nullable(fieldInfo.nullable())
.build();
}
......
......@@ -117,7 +117,7 @@ public class DefaultFields {
.docString("The validator used to check the correctness of the data.")
.nullable(true)
.defaultField(true)
.search(false)
.searchable(false)
.build();
/**
......
......@@ -103,7 +103,7 @@ public class Field {
/**
* 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.
......
......@@ -43,6 +43,11 @@ public class PersistenceHandlerWriter extends VelocitySourceWriter {
*/
private static final String CLASS = "class";
/**
* Context key for interface of PersistenceHandlers
*/
private static final String INTERFACE = "interface";
/**
* Context key for collection.
*/
......@@ -60,14 +65,16 @@ public class PersistenceHandlerWriter extends VelocitySourceWriter {
@Builder
public PersistenceHandlerWriter(
String packageName, String elementInterface, String className,
String implementation, String collection, @NonNull Fields fields
String packageName, String elementInterface, String interfaceName,
String className, String implementation, String collection,
@NonNull Fields fields
) {
super();
this.template = PERSISTENCE_HANDLER_TEMPLATE;
this.context.put(PACKAGE, packageName);
this.context.put(ELEMENT_INTERFACE, elementInterface);
this.context.put(CLASS, className);
this.context.put(INTERFACE, interfaceName);
this.context.put(COLLECTION, collection);
this.context.put(IMPLEMENTATION, implementation);
this.context.put(FIELDS, fields);
......
## Declarations
#set($interface = "IPersistenceHandler")
##
#if($package)
package $package;
#end
import java.util.Map;
import java.util.UUID;
import org.bson.Document;
import org.eclipse.ice.dev.annotations.IDataElement;
import org.eclipse.ice.dev.annotations.IPersistenceHandler;
import org.eclipse.ice.dev.annotations.PersistenceFilters;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
......@@ -46,7 +38,6 @@ public class $class implements $interface<$elementInterface> {
/**
* Save the $elementInterface.
* @param <T> Object extending IDataElement
* @param element
* @throws Exception
*/
......@@ -57,7 +48,6 @@ public class $class implements $interface<$elementInterface> {
/**
* Find and retrieve all $elementInterface from the collection.
* @param <T>
* @return an iterable of the retrieved elements.
* @throws Exception
*/
......@@ -74,26 +64,26 @@ public class $class implements $interface<$elementInterface> {
*/
@Override
public long clear() throws Exception {
long count = this.collection.count();
long count = this.collection.countDocuments();
this.collection.drop();
return count;
}
#macro(findmethod $var $type $docName)
#if(${var.Getter} && ${var.Search})
#if(${var.Unique})
#foreach($field in $fields)
#if(${field.Getter} && ${field.Searchable})
#if(${field.Unique})
/**
* Find $elementInterface by ${var.Name}.
* @param ${var.VarName}
* Find $elementInterface by ${field.Name}.
* @param ${field.VarName}
* @return found element or null
*/
#if(${var.DefaultField})
#if(${field.DefaultField})
@Override
#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
.find(PersistenceFilters.eq("${docName}", ${var.VarName}))
.find(PersistenceFilters.eq("${field.VarName}", ${field.VarName}))
.first();
if (doc == null) {
return null;
......@@ -104,21 +94,18 @@ public class $class implements $interface<$elementInterface> {
#else
/**
* Find $elementInterface by ${var.Name}.
* @param ${var.VarName}
* Find $elementInterface by ${field.Name}.
* @param ${field.VarName}
* @return Iterator of results
*/
#if(${var.DefaultField})
#if(${field.DefaultField})
@Override
#end
public Iterable<$elementInterface> findBy${var.NameForMethod}(${var.Type} ${var.VarName}) throws Exception {
return this.collection.find(PersistenceFilters.eq("${var.VarName}", ${var.VarName}))
public Iterable<$elementInterface> findBy${field.NameForMethod}(#fieldtype ${field.VarName}) throws Exception {
return this.collection.find(PersistenceFilters.eq("${field.VarName}", ${field.VarName}))
.map(doc -> mapper.convertValue(doc, ${implementation}.class));
}
#end## if unique
#end## if getter and search
#end## macro findmethod
#foreach($field in $fields)
#findmethod($field ${field.Type} ${field.VarName})
#end
#end## foreach
}
\ 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;
import static org.junit.jupiter.api.Assertions.fail;
/*******************************************************************************
* 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
*******************************************************************************/
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
package org.eclipse.ice.tests.dev.annotations.processors;
import static com.google.testing.compile.Compiler.*;
import static com.google.testing.compile.CompilationSubject.*;
import javax.annotation.processing.Processor;
import javax.tools.JavaFileObject;
import org.eclipse.ice.dev.annotations.processors.DataElementProcessor;
import org.junit.jupiter.api.Test;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import lombok.AllArgsConstructor;
......@@ -38,6 +40,12 @@ import lombok.AllArgsConstructor;
*/
class DataElementProcessorTest {
/**
* Helper for testing DataElement related annotations.
*/
private static DataElementAnnotationTestHelper helper =
new DataElementAnnotationTestHelper();
/**
* Fully qualified name of the generated interface.
*/
......@@ -53,7 +61,7 @@ class DataElementProcessorTest {
* @author Daniel Bluhm
*/
@AllArgsConstructor
private static enum Inputs {
private static enum Inputs implements JavaFileObjectResource{
HELLO_WORLD("HelloWorld.java"),
NAME_MISSING("DataElementNameMissing.java"),
ON_ENUM("DataElementOnEnum.java"),
......@@ -75,19 +83,16 @@ class DataElementProcessorTest {
/**
* Parent directory of inputs. Prepended to all paths.
*/
private static final String PARENT = "input/";
private static final String PARENT = "input/DataElement/";
/**
* Path to inputs.
*/
private String path;
private String filename;
/**
* Retrieve the JavaFileObject corresponding to this input.
* @return input as a JavaFileObject
*/
public JavaFileObject get() {
return JavaFileObjects.forResource(PARENT + this.path);
@Override
public String getPath() {
return PARENT + this.filename;
}
}
......@@ -96,7 +101,7 @@ class DataElementProcessorTest {
* @author Daniel Bluhm
*/
@AllArgsConstructor
private static enum Patterns {
private static enum Patterns implements JavaFileObjectResource {
DEFAULTS_INT("Defaults.java"),
DEFAULTS_IMPL("DefaultsImplementation.java"),
SINGLE_INT("Single.java"),
......@@ -116,61 +121,19 @@ class DataElementProcessorTest {
/**
* Parent directory of inputs. Prepended to all paths.
*/
private static final String PARENT = "patterns/";
private static final String PARENT = "patterns/DataElement/";
/**
* 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
public String getPath() {
return PARENT + this.filename;
}
}
/**
* 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 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
*/
private static Compilation compile(JavaFileObject... sources) {
return javac()
.withProcessors(
getLombokAnnotationProcessor(),
new DataElementProcessor()
).compile(sources);
}
/**
* Assert that the interface generated in this compilation matches the given
* pattern.
......@@ -213,7 +176,7 @@ class DataElementProcessorTest {
*/
@Test
void testNoAnnotationsToProcessSucceeds() {
Compilation compilation = compile(Inputs.HELLO_WORLD.get());
Compilation compilation = helper.compile(Inputs.HELLO_WORLD.get());
assertThat(compilation).succeeded();
}
......@@ -223,7 +186,7 @@ class DataElementProcessorTest {
*/
@Test
void testMissingNameFails() {
Compilation compilation = compile(Inputs.NAME_MISSING.get());
Compilation compilation = helper.compile(Inputs.NAME_MISSING.get());
assertThat(compilation)
.hadErrorContaining(
"missing a default value for the element 'name'"
......@@ -235,7 +198,7 @@ class DataElementProcessorTest {
*/
@Test
void testAnnotateInterfaceFails() {
Compilation compilation = compile(Inputs.ON_INTERFACE.get());
Compilation compilation = helper.compile(Inputs.ON_INTERFACE.get());
assertThat(compilation)
.hadErrorContaining("DataElementSpec must be class");
}
......@@ -245,7 +208,7 @@ class DataElementProcessorTest {
*/
@Test
void testAnnotateEnumFails() {
Compilation compilation = compile(Inputs.ON_ENUM.get());
Compilation compilation = helper.compile(Inputs.ON_ENUM.get());
assertThat(compilation)
.hadErrorContaining("DataElementSpec must be class");
}
......@@ -256,7 +219,7 @@ class DataElementProcessorTest {
*/
@Test
void testNoDataFieldsSucceeds() {
Compilation compilation = compile(Inputs.NO_DATAFIELDS.get());
Compilation compilation = helper.compile(Inputs.NO_DATAFIELDS.get());
assertDefaultsPresent(compilation);
}
......@@ -265,7 +228,7 @@ class DataElementProcessorTest {
*/
@Test
void testWithSingleDataFieldSucceeds() {
Compilation compilation = compile(Inputs.SINGLE.get());
Compilation compilation = helper.compile(Inputs.SINGLE.get());
assertDefaultsPresent(compilation);
assertInterfaceMatches(compilation, Patterns.SINGLE_INT.get());
assertImplementationMatches(compilation, Patterns.SINGLE_IMPL.get());
......@@ -276,7 +239,7 @@ class DataElementProcessorTest {
*/
@Test
void testWithManyDataFieldsSucceeds() {
Compilation compilation = compile(Inputs.MANY.get());
Compilation compilation = helper.compile(Inputs.MANY.get());
assertDefaultsPresent(compilation);
assertInterfaceMatches(compilation, Patterns.MANY_INT.get());
assertImplementationMatches(compilation, Patterns.MANY_IMPL.get());
......@@ -287,7 +250,7 @@ class DataElementProcessorTest {
*/
@Test
void testSingleNonPrimitiveDataFieldSucceeds() {
Compilation compilation = compile(Inputs.SINGLE_NON_PRIMITIVE.get());
Compilation compilation = helper.compile(Inputs.SINGLE_NON_PRIMITIVE.get());
assertDefaultsPresent(compilation);
assertInterfaceMatches(compilation, Patterns.SINGLE_NON_PRIMITIVE_INT.get());
assertImplementationMatches(compilation, Patterns.SINGLE_NON_PRIMITIVE_IMPL.get());
......@@ -298,7 +261,7 @@ class DataElementProcessorTest {
*/
@Test
void testManyNonPrimitiveDataFieldSucceeds() {
Compilation compilation = compile(Inputs.MANY_NON_PRIMITIVE.get());
Compilation compilation = helper.compile(Inputs.MANY_NON_PRIMITIVE.get());
assertDefaultsPresent(compilation);
assertInterfaceMatches(compilation, Patterns.MANY_NON_PRIMITIVE_INT.get());
assertImplementationMatches(compilation, Patterns.MANY_NON_PRIMITIVE_IMPL.get());
......@@ -310,7 +273,7 @@ class DataElementProcessorTest {
*/
@Test
void testDocStringsPreserved() {
Compilation compilation = compile(Inputs.SINGLE.get());
Compilation compilation = helper.compile(Inputs.SINGLE.get());
assertThat(compilation).generatedSourceFile(IMPLEMENTATION)
.contentsAsUtf8String()
.contains("* A UNIQUE STRING IN THE DOC STRING.");
......@@ -325,7 +288,7 @@ class DataElementProcessorTest {
*/
@Test
void testAccessibilityPreserved() {
Compilation compilation = compile(Inputs.ACCESSIBILITY_PRESERVED.get());
Compilation compilation = helper.compile(Inputs.ACCESSIBILITY_PRESERVED.get());
assertImplementationMatches(compilation, Patterns.ACCESSIBILITY_PRESERVED.get());
}
......@@ -337,7 +300,7 @@ class DataElementProcessorTest {
*/
@Test
void testDataFieldOnClassFails() {
Compilation compilation = compile(Inputs.DATAFIELD_ON_CLASS.get());
Compilation compilation = helper.compile(Inputs.DATAFIELD_ON_CLASS.get());
assertThat(compilation)
.hadErrorContaining("annotation type not applicable");
}
......@@ -347,7 +310,7 @@ class DataElementProcessorTest {
*/
@Test
void testDataFieldOnMethodFails() {
Compilation compilation = compile(Inputs.DATAFIELD_ON_METHOD.get());
Compilation compilation = helper.compile(Inputs.DATAFIELD_ON_METHOD.get());
assertThat(compilation)
.hadErrorContaining("annotation type not applicable");
}
......@@ -357,7 +320,7 @@ class DataElementProcessorTest {
*/
@Test
void testDataFieldGetterOption() {