My processor return a List of Object but I don't find a solution to override the MongoItemWriter to write some List of object and not object.
I tried this.
My processor :
#Override
public List<PlaqueLueEntity> process(PlaqueSousSurveillanceEntity item) {
log.trace("Traitement d'une entrée PlaqueSousSurveillanceEntity: {}", item);
List<PlaqueLue> rapprochements = this.rapprochementUseCase.findRapprochementByPlaque(item.getPlaque());
if (rapprochements.isEmpty()) {
return null;
}
for (PlaqueLue rapprochement : rapprochements) {
rapprochement.setRapprochement(true);
rapprochement.setHorodatageRapprochement(LocalDateTime.now());
}
List<PlaqueLueEntity> plaqueLueEntities =
rapprochements.stream().map(this.plateDetectionMapper::plateDetectionToPlateDetectionEntity).toList();
return plaqueLueEntities;
}
My writer
public class RapprochementMongoWriter<T> extends MongoItemWriter<List<T>> {
private MongoOperations template;
private MongoItemWriter<T> delegate;
#Override
public void write(final List<? extends List<T>> lists) throws Exception {
for (final List<T> list : lists) {
delegate.write(list);
}
}
#Bean
public MongoItemWriter<List<PlaqueLueEntity>> writer(MongoTemplate mongoTemplate,
BatchConfigurationProperties batchConfigurationProperties) {
MongoItemWriter<List<PlaqueLueEntity>> writer = new MongoItemWriter<>();
writer.setTemplate(mongoTemplate);
writer.setCollection(String.valueOf(CollectionEnum.COLLECTION.PLAQUE_LUE));
return writer;
}
And I defined in my BatchConfiguration:
#Bean
public MongoItemWriter<List<PlaqueLueEntity>> rapprochementWriter() {
return new RapprochementMongoWriter().writer(mongoTemplate, batchConfigurationProperties);
}
But before I can debug on my writer I got this error:
class org.bson.Document cannot be cast to class java.util.Collection (org.bson.Document is in unnamed module of loader 'app'; java.util.Collection is in module java.base of loader 'bootstrap')
And :
Attempt to update step execution id=1 with wrong version (1), where current version is 2
I don't find a solution to override the MongoItemWriter to write some List of object and not object.
The way you defined your custom item writer is correct:
public class RapprochementMongoWriter<T> extends MongoItemWriter<List<T>> {
private MongoItemWriter<T> delegate;
//...
}
However, you should not call the delegate for each list like this:
#Override
public void write(final List<? extends List<T>> lists) throws Exception {
for (final List<T> list : lists) {
delegate.write(list);
}
}
What you should do instead is "flatten" the items in a single list and call the delegate once, something like this:
#Override
public void write(final List<? extends List<T>> lists) throws Exception {
List<T> flatList = new ArrayList();
for (final List<T> list : lists) {
for (T item: list) {
flatList.add(item);
}
}
delegate.write(flatList);
}
Please note that I did not compile that snippet, so I will let you adapt it if needed, but you got the idea.
Related
I have a class that loaded data from scenario steps
my first class is LoadUserStepDfn
public class LoadUserStepDfn extends LoadDataStepDfn<User> {
public LoadUserStepDfn(ReadingUserUsingPoiji readingUserUsingPoiji) {
super.readingExcelUsingPoiji = readingUserUsingPoiji;
}
#Given("^Data is loaded from \"([^\"]*)\"$")
public void data_is_loaded_from (String filePath) throws Throwable {
super.data_is_loaded_from(filePath);
}
and it call class named LoadDataStepDfn
public class LoadDataStepDfn<T> {
public List<T> data;
protected ReadingExcelUsingPoiji readingExcelUsingPoiji;
public void data_is_loaded_from (String filePath) throws Throwable {
data = readingExcelUsingPoiji.TransformExcelToClass(filePath);
}
and here is my class that reads excel and store it to java class
public abstract class ReadingExcelUsingPoiji<T> {
public List<T> TransformExcelToClass(String filePath){
PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().addListDelimiter(";").build();
List<T> data = Poiji.fromExcel(new File(filePath), getMyType(), options);
return data;
}
public abstract Class<T> getMyType();
}
the problem that I want to use one class I don't want it to be abstract and use another one wiche is this class
public class ReadingUserUsingPoiji extends ReadingExcelUsingPoiji<User> {
public Class<User> getMyType(){
return User.class;
}
I am trying to understand here, so you dont want #override, but rather 1 method that returns you the type of class to transform to??
Why can't it be that simple... You have a method that determines what class you should use to transform to...
I dont understand why you are using generics...your logic doesnt seem to really care for it? Especially if you have 1 ReadingExcelUsingPoiji class..it really shouldnt care.
public class ReadingExcelUsingPoiji<T> {
public List<T> transformExcelToClass(String filePath, Class<T> classToTransformTo) {
PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().addListDelimiter(";").build();
List<T> data = Poiji.fromExcel(new File(filePath), classToTransformTo, options);
return data;
}
public static void main(String [] args) {
ReadingExcelUsingPoiji genericConverter = new ReadingExcelUsingPoiji();
List<User> listOfUsers = genericConverter.transformExcelToClass("yourFilePath", User.class);
List<Car> listOfCars = genericConverter.transformExcelToClass("yourFilePath", Car.class);
}
}
public class LoadUserStepDfn extends LoadDataStepDfn<User> {
#Given("^Data is loaded from \"([^\"]*)\"$")
public void data_is_loaded_from (String filePath) throws Throwable {
super.data_is_loaded_from(filePath , User.class);
}
}
public class LoadDataStepDfn<T> {
public List<T> data;
protected ReadingExcelUsingPoiji readingExcelUsingPoiji;
protected void data_is_loaded_from(String filePath, Class<T> classToTransformTo) throws Throwable {
data = readingExcelUsingPoiji.transformExcelToClass(filePath, classToTransformTo);
}
}
I got several objects that are created based on DataObject class.
So I'd like to make generic factory to construct them.
I'v tried something like this:
interface FactoryObject<T> {
T create(DataObject data);
}
public class Factory {
List<FactoryObject> fromDataObjectArray(DataObject[] data, Class<? extends FactoryObject> cls) {
return Arrays.stream(Optional.ofNullable(data).orElse(new DataObject[0]))
.map(d -> cls.create()).collect(Collectors.toList());
}
}
Finally, I'd like to call
List<MyClass> myClasses = Factory.fromDataObjectArray(data, MyClass.class);
But method create() cannot be resolved, how can I achieve what I need?
If I understand you code, you want to create a list of instances; you could do something like that:
<T> List<T> fromDataObjectArray(DataObject[] data, FactoryObject<T> fac) {
return Arrays.stream(Optional.ofNullable(data).orElse(new DataObject[0]))
.map(d -> fac.create(d)).collect(Collectors.toList());
}
UPDATE:
If I understand your comment below, you want a composite factory that will determine from a DataObject what is the actual factory you want to use to create your instance.
You could do something like this:
public class CompositeFactory<T> implements FactoryObject<T> {
private final Function<DataObject,FactoryObject<? extends T>>[] funcs;
public CompositeFactory(
Function<DataObject,FactoryObject<? extends T>>... funcs) {
this.funcs = funcs;
}
#Override
public T create(DataObject data) {
for (Function<DataObject,FactoryObject<? extends T>> func: funcs) {
FactoryObject<? extends T> fac = func.apply(data);
if (fac != null) {
return fac.create(data);
}
}
return null; // or throw an exception
}
}
Another way to do that is conditional factories:
public class ConditionalFactory<T> implements FactoryObject<T> {
private final Predicate<DataObject> cond;
private final FactoryObject<? extends T> ifFac;
private final FactoryObject<? extends T> elseFac;
public ConditionalFactory(Predicate<DataObject> cond,
FactoryObject<? extends T> ifFac,
FactoryObject<? extends T> elseFac) {
this.cond = cond;
this.ifFac = ifFac;
this.elseFac = elseFac;
}
#Override
public T create(DataObject data) {
return (cond.test(data) ? ifFac : elseFac).create(data);
}
}
UPDATE 2:
Exemple: let's say you have the following classes:
class MyClass1 extends MyClass {
public MyClass1(DataObject data) {
}
}
class MyClass2 extends MyClass {
public MyClass2(DataObject data) {
}
}
...
and the corresponding factories:
FactoryObject<MyClass1> fac1 = (data) -> new MyClass1(data);
FactoryObject<MyClass2> fac2 = (data) -> new MyClass2(data);
FactoryObject<MyClass3> fac3 = (data) -> new MyClass3(data);
...
and let's say you can determine the actual class from the value of DataObject.getType():
You could do:
FactoryObject<MyClass> fact = new CompositeFactory<MyClass>(
(data)-> data.getType().equals("value1") ? fac1 : null,
(data)-> data.getType().equals("value2") ? fac2 : null,
(data)-> data.getType().equals("value3") ? fac3 : null,
...
);
you could also do:
FactoryObject<MyClass> fac = new CompositeFactory<MyClass>(
(data)->{
switch (data.getType()) {
case "value1":
return fac1;
case "value2":
return fac2;
...
default:
return null;
}
});
or:
FactoryObject<MyClass> fac = new ConditionalFactory<MyClass>(
(data)->data.getType().equals("value1"), fac1,
new ConditionalFactory<MyClass>(
(data)->data.getType().equals("value2"), fac2,
fac3));
Our processor returns a List<?> (effectively passing a List<List<?>>) to our ItemWriter.
Now, we observed that the JdbcBatchItemWriter is not programmed to handle item instanceof List. We also observed to process item instanceof List; we need to write a custom ItemSqlParameterSourceProvider.
But the sad part is that it returns SqlParameterSource which can handle only one item and again not capable of handling a List.
So, can someone help us understand how to handle list of lists in the JdbcBatchItemWriter?
Typically, the design pattern is:
Reader -> reads something, returns ReadItem
Processor -> ingests ReadItem, returns ProcessedItem
Writer -> ingests List<ProcessedItem>
If your processor is returning List<Object>, then you need your Writer to expect List<List<Object>>.
You could do this by wrapping your JdbcBatchItemWriter as a delegate in an ItemWriter that looks something like this:
public class ListUnpackingItemWriter<T> implements ItemWriter<List<T>>, ItemStream, InitializingBean {
private ItemWriter<T> delegate;
#Override
public void write(final List<? extends List<T>> lists) throws Exception {
final List<T> consolidatedList = new ArrayList<>();
for (final List<T> list : lists) {
consolidatedList.addAll(list);
}
delegate.write(consolidatedList);
}
#Override
public void afterPropertiesSet() {
Assert.notNull(delegate, "You must set a delegate!");
}
#Override
public void open(ExecutionContext executionContext) {
if (delegate instanceof ItemStream) {
((ItemStream) delegate).open(executionContext);
}
}
#Override
public void update(ExecutionContext executionContext) {
if (delegate instanceof ItemStream) {
((ItemStream) delegate).update(executionContext);
}
}
#Override
public void close() {
if (delegate instanceof ItemStream) {
((ItemStream) delegate).close();
}
}
public void setDelegate(ItemWriter<T> delegate) {
this.delegate = delegate;
}
}
public class ListUnpackingItemWriter<T> implements FlatFileItemWriter<List<T>>, ItemStream, InitializingBean {
#Override
public void afterPropertiesSet() {
setLineAggregator(item -> String.join("\n", item.stream().map(T::toString).collect(Collectors.toList())));
}
}
Just added a custom line aggregator to the above solution, this helps in writing the content to a file by using FlatFileItemWriter<List<T>>. You can replace T with actual class name to avoid compilation error while calling toString() method.
During annotation processing I am currently processing the annotation of a method:
#Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Messager msg = processingEnv.getMessager();
for (TypeElement te : elements) {
for (Element e : env.getElementsAnnotatedWith(te)) {
processAnnotation(e, msg);
}
}
return true;
}
private void processAnnotation(Element method, Messager msg) {
final Info ann = method.getAnnotation(Info.class);
assert method.getKind() == ElementKind.METHOD;
....
I can get to the types (or its mirrors) of the parameters with
final ExecutableType emeth = (ExecutableType)method.asType();
final List<? extends TypeMirror> parameterTypes = emeth.getParameterTypes();
but how to I get to its annotations? I would like to check, if the method under consideration has any parameter with the annotation #Input. For example the processed source could be:
#Info
void myMethodOk(#Input String input) { }
#Info
void myMethodNotOk(#Input String input) { }
If you cast your method Element to ExecutableElement, then you can invoke executableElement.getParamerers(). This returns a list of VariableElements, which you can get annotations from.
I wish to have all Lists deserialized to unmodifiable collections.
Here is my class that I am deserializing:
public class MyClass
{
public final mylist = Collections.unmodifiablelist(new ArrayList());
}
How I deserialize:
MyClass inst = (new Gson()).fromJson("{mylist:[\"firstString\"]}", MyClass.class);
I don't want to have to create a register a type adapter for all the 100 classes that use a List, so is there a way to globally override the deserializable of List for that instance of Gson?
Why don't you just follow the JavaBean standards and just return unmodifiable lists?
public class MyClass {
private List<String> myList = new ArrayList<String>();
public void setMyList(List<String> list) {
this.myList = list;
}
public Collection<String> getMyList() {
return Collections.unmodifiablelist(this.myList);
}
}
I would go with an adapter factory too, but shorter code :
class MyAdapter implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> tokenType) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, tokenType);
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
#Override
public T read(JsonReader arg0) throws IOException {
T t = delegate.read(arg0);
if (List.class.isAssignableFrom(tokenType.getRawType())) {
List<?> list = (List<?>) t;
return (T) Collections.unmodifiableList(list);
}
return t;
}
};
}
}
Then, as usual, register it :
Gson g = new GsonBuilder().registerTypeAdapterFactory(new MyAdapter()).create();