BIRT: scripted datasource cannot access public methods - birt

I´m trying to set up a scripted Datasource using pojos from my app (java 1.8).
I embedded a reportengine (4.4.0) in my app via maven and created a report with a scripted datasourse. The pojos can be seen by the report.
bookSrv = Packages.de.me.service.ReportDataService;
works.
I can access static methods of my object, but whatever i try, accessing public methods or fields results in an exception.
So
books = bookSrv.books;
gives
org.mozilla.javascript.EvaluatorException: Java class "de.me.service.ReportDataService" has no public instance field or method named "books". (/report/data-sets/script-data-set[#id="5"]/method[#name="open"]#11)
or
books = bookSrv.showMyBooks('Peter');
gives
org.mozilla.javascript.EvaluatorException: Java class "de.my.service.ReportDataService" has no public instance field or method named "showMyBooks". (/report/data-sets/script-data-set[#id="5"]/method[#name="open"]#11)
But:
books = Packages.me.tdesk.service.ReportDataService.myBooksStatic('Peter');
works like expected. (Simply a static wrapper around my method)
here is my java class:
package de.me.service;
import de.me.model.dto.BookDto;
import java.util.ArrayList;
import java.util.List;
public class ReportDataService {
public ReportDataService() {
books = showMyBooks("Peter");
System.out.println("ReportDataService.init()");
}
private List<BookDto> books;
public List<BookDto> getBooks() {
return books;
}
public void setBooks(List<BookDto> books) {
this.books = books;
}
public List<BookDto> showMyBooks(String commodity){
BookDto dto = new BookDto(12l, "otto", "My", 12l);
List<BookDto> books = new ArrayList<>();
books.add(dto);
dto = new BookDto(122l, "paul", "My", 122l);
books.add(dto);
return books;
}
public static List<BookDto> myBooksStatic(String commodity){
ReportDataService rep = new ReportDataService();
return rep.showMyBooks(commodity);
}
}
Many thanks
Uli

Hmm...
shouldn't it be
bookSrv = new Packages.de.me.service.ReportDataService(...);
instead of
bookSrv = Packages.de.me.service.ReportDataService;
?
With the second one, bookSrv will be the Java class ReportDataService (which is an object in Rhinoscript), not an instance of the class.

Related

Mapping fields under #RequestBody

I would like to newer have nulls for my fields which are type of "list"
As I understead below are points where object are initializated, so in these we should do something to initializate empty list in case of null.
Controller (When object is comming from frontend)
Initialization (new AnyObject() or AnyObject.toBuilder - lombok)
FeginClient - Calls between Apis
Is there some framework/annotation which in case of null will set empty list?
Below is what I have currently done
public class TestMapin {
public static void main(String[] args) throws IllegalAccessException {
Test test = new Test();
notNull(test);
System.out.println(test);
}
public static void notNull(Object test) throws IllegalAccessException {
for (Field field : test.getClass().getDeclaredFields()) {
boolean access = field.canAccess(test);
field.setAccessible(true);
if (field.get(test) == null) {
if (field.getType().isAssignableFrom(List.class)) {
field.set(test, Collections.emptyList());
} else if (field.getType().isAssignableFrom(Map.class)) {
field.set(test, Collections.emptyMap());
}
} else if (field.getType().getPackageName().contains("org.owozniak.selfestem")) {
notNull(field);
}
field.setAccessible(access);
}
}
}
#Setter
#ToString
class Test {
private ArrayList<String> stringList;
private Box box = Box.builder().build();
private Magazine magazine;
}
So, I need to use
- initializating via toBuilder - #Singular annotation
- Controllers/Feign clients - inject this code snippet to spring filters
- Initialization via constructor - Use static factory method which will use this code snipped and return "enchanced" instance
Any more ideas/frameworks?
I suggest to use #Singular annotation along with #Builder. This will initialize collection with a non-null List. By annotating one of the parameters (if annotating a method or constructor with #Builder) or fields (if annotating a class with #Builder) with the #Singular annotation, lombok will treat that builder node as a collection, and it generates 2 ‘adder’ methods instead of a ‘setter’ method. One which adds a single element to the collection, and one which adds all elements of another collection to the collection. No setter to just set the collection (replacing whatever was already added) will be generated. A ‘clear’ method is also generated. You can read full details on my recent article https://technicalsand.com/using-lombok-advanced-features/
import lombok.Builder;
import lombok.Singular;
import java.util.Set;
#Builder
public class BuilderExample {
#Builder.Default private long created = System.currentTimeMillis();
private String name;
private int age;
#Singular
private Set<String> occupations;
}

Spring + MongoDB tag #Query with $group not working

NOTE: Go down in order to see the edited message.
I'm trying to imitate this query:
db.sentiments.aggregate([
{"$group" : {_id:{theme_id:"$theme",sentiment_id:"$sentiment"}, count:{$sum:1}}},
{"$sort":{"_id.theme_id":1}} ])
This is the code that I had generated in order to imitate it:
#RepositoryRestResource(collectionResourceRel = "sentiments", path = "sentiments")
public interface SentimentsRepository extends MongoRepository<Sentiments, String> {
Long countByTheme(#Param("theme") String theme);
#Query(value ="[\n" +
" {\"$group\" : {_id:{theme_id:\"$theme\",sentiment_id:\"$sentiment\"}, count:{$sum:1}}},\n" +
"\t{\"$sort\":{\"_id.theme_id\":1}}\n" +
"]",count = true)
List<Object> comptarSentiments();
}
Well this code is returning me this error:
"exception": "org.springframework.data.mongodb.UncategorizedMongoDbException",
"message": "Can't canonicalize query: BadValue unknown operator: $group; nested exception is com.mongodb.MongoException: Can't canonicalize query: BadValue unknown operator: $group",
Actually I'm a begginer in what refers to the use of Spring so I'm very lost, does any one know what should I do?
Thanks and sorry for my bad english, not my native language.
[EDIT]----------------------------------------
Just as the comment wrote by Shawn Clark It's not possible to do it this way, in order to achieve that you will need to create a customRepository.
What's the difference between Spring Data's MongoTemplate and MongoRepository?
I have been trying to do it this way but something doesn't seem to be correct, here is my new code:
#RepositoryRestResource(collectionResourceRel = "sentiments", path = "sentiments")
public interface SentimentsRepository extends CrudRepository<Sentiments, String>, CustomSentimentsRepository {
//Other methods...
}
public interface CustomSentimentsRepository {
List<CountResult> yourCustomMethod();
class CountResult{
String theme;
String sentiment;
int total;
}
}
public class SentimentsRepositoryImpl implements CustomSentimentsRepository {
private final MongoOperations operations;
#Autowired
public SentimentsRepositoryImpl(MongoOperations operations) {
Assert.notNull(operations, "MongoOperations must not be null!");
this.operations = operations;
}
#Override
public List<CountResult> yourCustomMethod(){
Aggregation agg = Aggregation.newAggregation(
Aggregation.group("theme","sentiment").count().as("total"),
Aggregation.project("theme","sentiment").and("total").previousOperation(),
Aggregation.sort(Sort.Direction.DESC, "theme")
);
//Convert the aggregation result into a List
AggregationResults<CountResult> groupResults
= operations.aggregate(agg,"sentiments", CountResult.class);
//List<CountResult> result = groupResults.getMappedResults();
return groupResults.getMappedResults();
}
}
I'm not even able to debbug this code and I'm always getting a 404.
Based on the information I have found you can't do that complex of a #Query on a MongoRepository method. In this case you would want to create a class and implement your comptarSentiments() method using the mongoTemplate to query the data store with your aggregate function. Then create a controller class that exposes a REST endpoint and have it call the repository.
Once you get to doing complex queries in Mongo you lose the ease of #RepositoryRestResource and have to go back to wiring the REST endpoint to the repository yourself.
Spring Data REST : custom query for MongoDB repository
Implementing custom methods of Spring Data repository and exposing them through REST
I finally managed to solve the problem, seems like it was related with the controller and the type of the atribute "total" from the innerClass CountResult, it needs to be a String (this is very important, otherwise the Aggregation.project will fail). Here goes the final code:
public interface CustomSentimentsRepository {
List<CountResult> myCountGroupByThemeAndSentiment();
class CountResult{
public String theme;
public String sentiment;
public String total;
}
}
public class SentimentsRepositoryImpl implements CustomSentimentsRepository {
private final MongoTemplate mongoTemplate;
#Autowired
public SentimentsRepositoryImpl(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Override
public List<CountResult> myCountGroupByThemeAndSentiment(){
Aggregation agg = Aggregation.newAggregation(
Aggregation.group("theme","sentiment").count().as("total"),
Aggregation.project("theme","sentiment").andInclude("total"),
Aggregation.sort(Sort.Direction.ASC,"theme","sentiment")
);
AggregationResults<CountResult> groupResults
= mongoTemplate.aggregate(agg,"sentiments", CountResult.class);
return groupResults.getMappedResults();
}
}
#RepositoryRestResource(collectionResourceRel = "sentiments", path = "sentiments")
public interface SentimentsRepository extends CrudRepository<Sentiments, String>, CustomSentimentsRepository {
//Other methods
}
#RestController
#RequestMapping(value = "sentiments/search")
public class ChartsController {
#Autowired
private SentimentsRepository sentimentsRepository;
#RequestMapping(value = "myCountGroupByThemeAndSentiment", method = RequestMethod.GET)
public ResponseEntity<?> yourCustomMethod() {
List<?> count=sentimentsRepository.myCountGroupByThemeAndSentiment();
return new ResponseEntity(count, HttpStatus.OK);
}
}
You can use #Aggrgation available in spring data mongodb 2.2.X versions:
#Aggregation(pipeline = {"{ '$group': { '_id' : '$lastname', names : { $addToSet : '$?0' } } }", "{ '$sort' : { 'lastname' : -1 } }"}) List<PersonAggregate> groupByLastnameAnd(String property);

Deserialise JSON fields based on user role

I have some fields in a model that I only want to be returned when the logged in user has the role ROLE_ADMIN. I can use #JsonIgnore but that hides it for everyone. How can I make it hide dynamically?
You should use Jackson Json Views technology to acheive it - it allows to choose a different set of fields to be serialized programatically. It is also supported by Spring
Consider you have a class Model with two properties: commonField which should be available for everyone and secretField which should be available only for certain users. You should create an hierarchy of views (any classes would work) and specify which field is available in which view using #JsonView annotation
package com.stackoverflow.jsonview;
import com.fasterxml.jackson.annotation.JsonView;
public class Model {
public static class Public {}
public static class Secret extends Public {}
#JsonView(Public.class)
private String commonField;
#JsonView(Secret.class)
private String secretField;
public Model() {
}
public Model(String commonField, String secretField) {
this.commonField = commonField;
this.secretField = secretField;
}
public String getCommonField() {
return commonField;
}
public void setCommonField(String commonField) {
this.commonField = commonField;
}
public String getSecretField() {
return secretField;
}
public void setSecretField(String secretField) {
this.secretField = secretField;
}
}
Now you can specify the view you want to use in concrete ObjectMapper
package com.stackoverflow.jsonview;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*/
public class ModelTest {
#Test
public void testSecretField() throws JsonProcessingException {
Model model = new Model("commonField","secretField");
assertEquals("{\"commonField\":\"commonField\",\"secretField\":\"secretField\"}", new ObjectMapper().writerWithView(Model.Secret.class).writeValueAsString(model));
assertEquals("{\"commonField\":\"commonField\"}", new ObjectMapper().writerWithView(Model.Public.class).writeValueAsString(model));
}
}
I am not sure if you can use declaratie approach to make spring choose the right view based on user role out of the box, so probably you will have to write some code like this:
#RequestMapping("/data")
public String getData(HttpServletRequest request) {
Model model = service.getModel();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper = request.isUserInRole("ROLE_ADMIN") ? objectMapper.writerWithView(Model.Secret.class) : objectMapper.writerWithView(Model.Public.class);
return objectMapper.writeValueAsString(model);
}
I solved this after literally a full month of trying various things. I'm working with Spring 4.3.1 and boot, with data being returned in Hal using a pagedrepository.
extend RepositoryRestMvcConfiguration as MyRepositoryRestMvcConfiguration and add #Configuration to the class, make sure your starter class has #EnableWebMvc
add this to MyRepositoryRestMvcConfiguration- extend TypeConstrainedMappingJackson2HttpMessageConverter as MyResourceSupportHttpMessageConverter
add this to MyRepositoryRestMvcConfiguration
#Override
#Bean
public TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter() {
ArrayList<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(MediaTypes.HAL_JSON);
if (config().useHalAsDefaultJsonMediaType()) {
mediaTypes.add(MediaType.APPLICATION_JSON);
}
int order = config().useHalAsDefaultJsonMediaType() ? Ordered.LOWEST_PRECEDENCE - 10
: Ordered.LOWEST_PRECEDENCE - 1;
TypeConstrainedMappingJackson2HttpMessageConverter converter = new MyResourceSupportHttpMessageConverter(
order);
converter.setObjectMapper(halObjectMapper());
converter.setSupportedMediaTypes(mediaTypes);
converter.getObjectMapper().addMixIn(Object.class, MyFilteringMixin.class);
final FilterProvider myRestrictionFilterProvider = new SimpleFilterProvider()
.addFilter("MyFilteringMixin", new MyPropertyFilter()).setFailOnUnknownId(false);
converter.getObjectMapper().setFilterProvider(myRestrictionFilterProvider);
return converter;
}
Create an empty Mixin
package filters;
import com.fasterxml.jackson.annotation.JsonFilter;
#JsonFilter("MyFilteringMixin")
public class MyFilteringMixin {}
Create an empty Mixin
create class MyPropertyFilter extending SimpleBeanPropertyFilter and override adapt this method
serializeAsField(Object, JsonGenerator, SerializerProvider, PropertyWriter)you need to call either super.serializeAsField(pPojo, pJgen, pProvider, pWriter) or pWriter.serializeAsOmittedField(pPojo, pJgen, pProvider) depending on whether you wish to include or discard this particular field.
I added an annotation to the particular fields I wanted to alter and interrogated that annotation when deciding which of these two to call. I injected the security role and stored permitted roles in the annotation.
This alters what Hal shares out to the caller, not what Hal is holding in its repository. Thus you can morph it depending on who the caller is.

Spring Data Rest - Add link to search endpoint

In our Spring-Data-Rest Project we have a custom (fuzzy) search on a /buergers/search/findBuergerFuzzy?searchString="..." endpoint.
Is it possible to add a link for it on the /buergers/search endpoint (Without overriding the automatically exposed Repository findBy Methods)?
The Controller exposing the search:
#BasePathAwareController
#RequestMapping("/buergers/search/")
public class BuergerSearchController {
#Autowired
QueryService service;
#RequestMapping(value = "/findBuergerFuzzy", method = RequestMethod.GET)
public
#ResponseBody
ResponseEntity<?> findBuergerFuzzy(PersistentEntityResourceAssembler assembler, #Param("searchString") String searchString) {
if (searchString.length() < 3)
throw new IllegalArgumentException("Search String must be at least 3 chars long.");
List<Buerger> list = service.query(searchString, Buerger.class, new String[]{"vorname", "nachname", "geburtsdatum", "augenfarbe"});
final List<PersistentEntityResource> collect = list.stream().map(assembler::toResource).collect(Collectors.toList());
return new ResponseEntity<Object>(new Resources<>(collect), HttpStatus.OK);
}
}
UPDATE: This is an outdated workaround answer. Upgrade to Spring HATEOAS 1.0.
Old Workaround:
Digging the spring-data-rest source i found the RepositorySearchesResource which seems to solve the problem.
#Component
public class SearchResourcesProcessor implements ResourceProcessor<RepositorySearchesResource> {
#Override
public RepositorySearchesResource process(RepositorySearchesResource repositorySearchesResource) {
final String search = repositorySearchesResource.getId().getHref();
final Link findFullTextFuzzy = new Link(search + "/findFullTextFuzzy{?q}").withRel("findFullTextFuzzy");
repositorySearchesResource.add(findFullTextFuzzy);
return repositorySearchesResource;
}
}
Because we generate this code by templates, this is sufficient and fullfills our needs. Make sure to check the comments for the right and safe way.
Version
migrate-to-1.0.changes
ResourceSupport is now RepresentationModel
Resource is now EntityModel
Resources is now CollectionModel
PagedResources is now PagedModel
Code
The code for new version:
import org.springframework.data.rest.webmvc.RepositorySearchesResource;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.server.RepresentationModelProcessor;
import org.springframework.stereotype.Component;
#Component
public class RepositorySearchesProcessor implements RepresentationModelProcessor<RepositorySearchesResource> {
#Override
public RepositorySearchesResource process(RepositorySearchesResource model) {
System.out.println(model.getDomainType());
model.add(Link.of(model.getRequiredLink("self").getHref() + "/findFullTextFuzzy{?q}").withRel("findFullTextFuzzy"));
return model;
}
}
How
About how to find what resource or model you use, after setting breakpoints in each method of RepresentationModel, you maybe find something useful :

Gson.toJson() and inheriting from a generic class

I have the following class:
public static class TestSomething {
Integer test;
public TestSomething(Integer test) {
this.test = test;
}
// getter and setter for test
}
Ok, now create a collection of this class and serialize it with Gson:
Collection<TestSomething> tests = Arrays.asList(
new TestSomething(1),
new TestSomething(2),
new TestSomething(3)
);
String json = new Gson().toJson(tests, new TypeToken<Collection<TestSomething>>() {}.getType());
After this, the String json is set to
[{"test":1},{"test":2},{"test":3}]
Which is great.
But now, all of my model classes inherit from a generic type Identifiable<T> which provides just two methods T getId() and void setId(T). So I change the TestSomething-class from above to
public static class TestSomething extends Identifiable<Long> {
// same as above
}
When I try to put this through Gson.toJson(), Gson ends up with the following Exception:
java.lang.UnsupportedOperationException: Expecting parameterized type, got class path.to.TestSomething.
Are you missing the use of TypeToken idiom?
See http://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Gener
at com.google.gson.TypeInfoFactory.getActualType(TypeInfoFactory.java:97)
...
So, what do I have to do to get this work?
I don't know the answer, but I know that generic type resolution is a tricky thing to get right: specifically full type resolution from interface with type parameter T up through to generic parameter declaration (T=Long). In these cases it is not enough to check for Method object's parameters but also resolve generic type parameters. This is most likely what causes issues; it may be a bug in Gson.
Since you are serializing things, perhaps you could just omit any type declarations? Although your TypeToken is correct for the use case, maybe it confuses Gson.
But just in case you could not make Gson work with this, I know that of other JSON libraries Jackson can handle such cases correctly.
Perhaps this issue was resolved in one of the Gson releases newer than what the original questioner was using, because the example in the original question now serializes as expected.
// output:
// [{"test":1},{"test":2},{"test":3}]
import java.util.Arrays;
import java.util.Collection;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
public class Foo
{
public static void main(String[] args)
{
Collection<TestSomething> tests = Arrays.asList(
new TestSomething(1),
new TestSomething(2),
new TestSomething(3));
String json = new Gson().toJson(tests, new TypeToken<Collection<TestSomething>>() {}.getType());
System.out.println(json);
}
}
class TestSomething extends Identifiable<Long>
{
Integer test;
public TestSomething(Integer test)
{
this.test = test;
}
#Override
Long getId()
{
return new Long(test);
}
#Override
void setId(Long t)
{
this.test = (int)(t.longValue());
}
}
abstract class Identifiable<T>
{
abstract T getId();
abstract void setId(T t);
}

Resources