Share a org.eclipse.microprofile.graphql.GraphQLApi in a JAR File - graphql

I'm writing a GraphQL api in java. I would like to provide this GraphQL api in a JAR file that this api implementation could be consumed/reused in other Java EE applications running in OpenLiberty 22. This is my api implementation.
import org.eclipse.microprofile.graphql.Description;
import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Name;
import org.eclipse.microprofile.graphql.NonNull;
import org.eclipse.microprofile.graphql.Query;
...
#GraphQLApi
#RequestScoped
public class SystemStatusGraphQL {
#Inject
private DbAdapter databaseAdapter;
#Query("system")
#NonNull
#Description("Gets status information about the system")
public SystemStatus getSystemStatus(#Name("name") String name) {
return database.getCurrentStatus(name);
}
}
I deployed this JAR file as maven package and consumed it in my target application and I have two seperate problems or questions now.
How to reuse this API so that #GraphQLApi is recognized by OpenLiberty? I tried to inherit from the api class but OpenLiberty does not load GraphQL endpoint.
public class MySystemStatusGraphQL extends com.test.stystem.status.api.SystemStatusGraphQL {
}
When I paste all the GraphQL stuff provided at the top and only try to reuse the model class SystemStatus, Jandex can not resolve object types. When starting OpenLibertyServer, this error occurs: Class [com.mylib.system.status.database.model.SystemStatus] is not indexed in Jandex. Can not scan Object Type, might not be mapped correctly. This error retains even if I create jandex index file on build and add it as part of the JAR file. The SystemStatus class contains all type definitions as you can see:
#Type("SystemStatus")
#Description("Describes current state of system.")
public class SystemStatus{
#NonNull
#Name("_id")
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#NonNull
#Name("serial")
private String serial;
public String getSerial() {
return this.serial;
}
}
I would prefer to reuse whole api which brings the issue mentioned in question 1. If this is not possible, how can I solve issue mentioned in question 2?

Related

Why is this method in a Spring Data repository considered a query method?

We have implemented an application that should be able to use either JPA, Couchbase or MongoDB. (for now, may increase in the future). We successfully implemented JPA and Couchbase by separating repositories for each e.g. JPA will come from org.company.repository.jpa while couchbase will come from org.company.repository.cb. All repository interfaces extends a common repository found in org.company.repository. We are now targeting MongoDB by creating a new package org.company.repository.mongo. However we are encountering this error:
No property updateLastUsedDate found for type TokenHistory!
Here are our codes:
#Document
public class TokenHistory extends BaseEntity {
private String subject;
private Date lastUpdate;
// Getters and setters here...
}
Under org.company.repository.TokenHistoryRepository.java
#NoRepositoryBean
public interface TokenHistoryRepository<ID extends Serializable> extends TokenHistoryRepositoryCustom, BaseEntityRepository<TokenHistory, ID> {
// No problem here. Handled by Spring Data
TokenHistory findBySubject(#Param("subject") String subject);
}
// The custom method
interface TokenHistoryRepositoryCustom {
void updateLastUsedDate(#Param("subject") String subject);
}
Under org.company.repository.mongo.TokenHistoryMongoRepository.java
#RepositoryRestResource(path = "/token-history")
public interface TokenHistoryMongoRepository extends TokenHistoryRepository<String> {
TokenHistory findBySubject(#Param("subject") String subject);
}
class TokenHistoryMongoRepositoryCustomImpl {
public void updateLastUsedDate(String subject) {
//TODO implement this
}
}
And for Mongo Configuration
#Configuration
#Profile("mongo")
#EnableMongoRepositories(basePackages = {
"org.company.repository.mongo"
}, repositoryImplementationPostfix = "CustomImpl",
repositoryBaseClass = BaseEntityRepositoryMongoImpl.class
)
public class MongoConfig {
}
Setup is the same for both JPA and Couchbase but we didn't encountered that error. It was able to use the inner class with "CustomImpl" prefix, which should be the case base on the documentations.
Is there a problem in my setup or configuration for MongoDB?
Your TokenHistoryMongoRepositoryCustomImpl doesn't actually implement the TokenHistoryRepositoryCustom interface, which means that there's no way for us to find out that updateLastUsedDate(…) in the class found is considered to be an implementation of the interface method. Hence, it's considered a query method and then triggers the query derivation.
I highly doubt that this works for the other stores as claimed as the code inspecting query methods is shared in DefaultRepositoryInformation.

How to configure Spring Data Solr with Repositories for multiple cores

Is there a detailed and complete explanation for setting up spring data with solr using repositories with multicore support?
The only dependency that need to be used for spring-data-solr is
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-solr</artifactId>
<version>1.5.2.RELEASE</version>
</dependency>
It downloads solrj dependency and this must not be overridden with later versions of solrj.
Also It is always preferable to use HttpSolrServer over EmbeddedSolrServer which is what we will be working with.
The Configuration class should look like this:
#Configuration
#EnableSolrRepositories(value = "com.package.",multicoreSupport = true)
public class SolrConfig
{
#Bean
public SolrServer solrServer() throws Exception
{
HttpSolrServerFactoryBean f = new HttpSolrServerFactoryBean();
f.setUrl("http://localhost:8983/solr");
f.afterPropertiesSet();
return f.getSolrServer();
}
#Bean
public SolrTemplate solrTemplate(SolrServer solrServer) throws Exception
{
return new SolrTemplate(solrServer());
}
}
The document entity should contain information about which core they belong to
#SolrDocument(solrCoreName = "core1")
public class Document1
{
#Id
#Field
private String id;
/**other attributes**/
}
The other document should be
#SolrDocument(solrCoreName = "core2")
public class Document2
{
#Id
#Field
private String id;
/**other attributes**/
}
Now the best part is you are already done. Simply setting up repository in the plain old way does the trick
public interface SolrCore1Repository extends SolrCrudRepository<Document1,String>
{
// optional code
}
the other repo is like
public interface SolrCore2Repository extends SolrCrudRepository<Document2,String>
{
// optional code
}
Once solr is running on the url specified and it has fields according to the pojo, you are done.

Hibernate: Populate Some Fields From Web Service

In our Spring MVC - Hibernate project we store uploaded files through a web service. However, we also keep information related to the file in a table in our database. A simplified version of the File class is as below:
#Entity
#Table(name="FILE")
public class File {
#Id
#Column(name="ID")
public int id;
#Column(name="NAME")
public String name;
#Transient
public byte[] data;
public int getId() {
}
public void setId(int id) {
}
public String getName() {
}
public void setName(String name) {
}
public byte[] getData() {
}
public void setData(byte[] data) {
}
}
We load the File entity from our database and fetch its data from a web service as below:
File file = dao.getFileById({file_id});
byte[] data = webService.getFileData({file_id});
file.setData(data);
We use the File entity in a lot of places in our code, and every time we write a query to fetch a File we have to call the web service method to load its data. Also we have to do this for other entities that have a mapping to the File class like below:
#Entity
#Table(name="PATIENT_FILE")
PatientFile {
...
#ManyToOne
#JoinColumn(name="FILE_ID")
File file;
...
}
Now we have to manually fetch the binary data of the file from the web service whenever we load a PatientFile from the database.
Is there a way to have Hibernate do this automatically so that whenever a File entity is loaded, Hibernate fetches its data from the web service to populate the data field of the File object?
NOTE: I know I can implement the Lifecycle interface in the File class and override the onLoad method to do this, but I need a more centralized solution. Plus it wouldn't be nice to call a data layer method from the model.
Yes, you can implement a Hibernate Interceptor. By implementing the onLoad method, which is called just before object initialization, you can make your web service call and populate the data. The method will have to check that it is a File object that you are loading.

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 :

Spring: How to inject a value to static field?

With this class
#Component
public class Sample {
#Value("${my.name}")
public static String name;
}
If I try Sample.name, it is always 'null'. So I tried this.
public class Sample {
public static String name;
#PostConstruct
public void init(){
name = privateName;
}
#Value("${my.name}")
private String privateName;
public String getPrivateName() {
return privateName;
}
public void setPrivateName(String privateName) {
this.privateName = privateName;
}
}
This code works. Sample.name is set properly. Is this good way or not? If not, is there something more good way? And how to do it?
First of all, public static non-final fields are evil. Spring does not allow injecting to such fields for a reason.
Your workaround is valid, you don't even need getter/setter, private field is enough. On the other hand try this:
#Value("${my.name}")
public void setPrivateName(String privateName) {
Sample.name = privateName;
}
(works with #Autowired/#Resource). But to give you some constructive advice: Create a second class with private field and getter instead of public static field.
Soruce of this info is this: https://www.baeldung.com/spring-inject-static-field
Spring uses dependency injection to populate the specific value when it finds the #Value annotation. However, instead of handing the value to the instance variable, it's handed to the implicit setter instead. This setter then handles the population of our NAME_STATIC value.
#RestController
//or if you want to declare some specific use of the properties file then use
//#Configuration
//#PropertySource({"classpath:application-${youeEnvironment}.properties"})
public class PropertyController {
#Value("${name}")//not necessary
private String name;//not necessary
private static String NAME_STATIC;
#Value("${name}")
public void setNameStatic(String name){
PropertyController.NAME_STATIC = name;
}
}
This is my sample code for load static variable
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class OnelinkConfig {
public static int MODULE_CODE;
public static int DEFAULT_PAGE;
public static int DEFAULT_SIZE;
#Autowired
public void loadOnelinkConfig(#Value("${onelink.config.exception.module.code}") int code,
#Value("${onelink.config.default.page}") int page, #Value("${onelink.config.default.size}") int size) {
MODULE_CODE = code;
DEFAULT_PAGE = page;
DEFAULT_SIZE = size;
}
}
For those who want to use ApplicationContext in the main class of a Spring Boot application, you can just use the return value of SpringApplication.run.
Although workarounds may need to be implemented, one should try to avoid them in most scenarios if possible. Spring is great at handling dependency injection and treats most objects as Singletons. This means that Spring can handle the creation of objects for you, and the injection of these objects at runtime. When combining this with the fact that your Spring managed bean is likely a Singleton, the use of static methods and variables is largely unnecessary. You can simply autowire in an instance of the object you are looking for at the constructor level or variable level and reference the non-static version of the method or variable. This is ideal and behaves similarly to a static reference. Non static variables are basically static because you are only ever using one instance of the object in every part of the code and because of dependency injection you are never handling the instantiation of the object, just like with a static reference! Great! Now I'm sure there are instances where you need the work around (i.e. you aren't using dependency injection or class is not a singleton), but try to not use workarounds if possible. Also this is just my 2 cents. Someone may be able to offer 3. (:
public class InjectableClass{
#Value("${my.value}")
private String myString;
public String nonStaticMethod(){
return myString;
}
}
public class LogicClass{
private InjectableClass injectableClass;
#Autowire
public LogicClass(InjectableClass injectableClass){
this.injectableClass = injectableClass;
}
public void logicClassMethod(){
System.out.println("Hey! Here is the value I set on myString: " +
injectableClass.nonStaticMethod() + ". That was
basically like using a static method!");
}
}

Resources