I want to develop a Quarkus application that made of multiple independent components (custom extensions). Now, I need to each extension has own qute template; How can I do that?!
Here is a solution:
Put resources/templates/hello.html and this class in the deployment part of your custom extension.
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletionStage;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import io.quarkus.qute.Engine;
import io.quarkus.qute.Template;
import org.apache.commons.io.IOUtils;
#Path("hi")
public class TestQuteTemplateInDeployment {
public Template getTemplateFromFile(String path2template) {
Engine engine = Engine.builder().addDefaults().build();
ClassLoader classLoader = getClass().getClassLoader();
String content = "<!doctype html><html></html>";
try {
InputStream inputStream = classLoader.getResourceAsStream(path2template);
content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
} catch (Exception e) {
;
}
Template helloTemplate = engine.parse(content);
return helloTemplate;
}
#GET
#Produces(MediaType.TEXT_HTML)
public CompletionStage<String> get() {
return getTemplateFromFile("templates/hello.html").data("msg", "Hi! I'm Here.").renderAsync();
}
}
Related
I'm trying to migrate data from an in house database to a software. The software has a REST api for this purpose, that expects a csv file.
A working curl call for this API endpoint looks like this:
curl -isk POST -H "customHeaderName:customHeaderValue" -H "Authorization: bearer $TOKEN" -F "data=#accounts.csv" <apiBaseUrl>/gate/account/import/group-accounts
My plan is to post the data directly to the REST endpoint with a spring boot application, without crating a physical csv file first.
My implementation looks like this, with "csvString" beeing a csv formatted String (e.g.: "acc_id,acc_name,acc_desc\r\n1,john.doe,this is john\r\n2,peter.parker,this is peter"):
(I removed this code and added the current version below.)
When I call postAccountsAndGroups(csvString); I get a 415 response indicating that my request Body is not a propper csv file.
EDIT:
It seems like the API endpoint requires a Multipart Form. Therfore I came up with something like this:
import static org.springframework.web.util.UriComponentsBuilder.fromUriString;
import my.package.common.configuration.WebClientConfig;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.service.spi.ServiceException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
#Service
#Slf4j
public class MyApiImpl implements MyApi {
private final WebClient client;
private final String apiBaseUrl;
public MyApiImpl(
#Qualifier(WebClientConfig.MY_API_CLIENT_CONFIG) WebClient client,
#Value("${external.api.myapi.baseUrl}") String apiBaseUrl) {
this.client = client;
this.apiBaseUrl = apiBaseUrl;
}
#Override
public Mono<HttpStatus> postAccountsAndGroups(String csvString) {
MultipartBodyBuilder builder = new MultipartBodyBuilder();
Resource byteArrayResource = new ByteArrayResource(csvString.getBytes(StandardCharsets.UTF_8));
builder.part("data", byteArrayResource);
return client.post()
.uri(createAccountsUri())
.header("customHeaderName", "customHeaderValue")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(builder.build()))
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(HttpStatus.class).thenReturn(response.statusCode());
} else {
throw new ServiceException("Error uploading file");
}
});
}
private URI createAccountsUri() {
return fromUriString(apiBaseUrl).path("/gate/account/import/group-accounts").build().toUri();
}
}
Now I get 400 Bad Request as response though.
I stil havend found a way to implement my prefered solution. However I came up with this workaround, that relies on persisting the csv file:
In my case I chose "/tmp/account.csv" as file path since my application runs in a docker container with linux os. On a Windows machine you could use something like "C:/myapp/account.csv". The file path is injected vie the application.properties file using the custom value "migration.files.accounts" so it can be configured later.
import static org.springframework.web.util.UriComponentsBuilder.fromUriString;
import my.package.common.configuration.WebClientConfig;
import java.io.File;
import java.io.PrintWriter;
import java.net.URI;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.service.spi.ServiceException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
#Service
#Slf4j
public class PrimedexApiImpl implements PrimedexApi {
private final WebClient client;
private final String apiBaseUrl;
private final FileSystemResource accountsFile;
private final String accountsFilePath;
public PrimedexApiImpl(
#Qualifier(WebClientConfig.MY_API_CLIENT_CONFIG) WebClient client,
#Value("${external.api.api.baseUrl}") String apiBaseUrl,
#Value("${migration.files.accounts}") String accountsFilePath) {
this.client = client;
this.apiBaseUrl = apiBaseUrl;
this.accountsFilePath = accountsFilePath;
this.accountsFile = new FileSystemResource(accountsFilePath);
}
#Override
public Mono<HttpStatus> postAccountsAndGroups(String csvString) {
File csvOutputFile = new File(accountsFilePath);
if (csvOutputFile.delete()) {
log.info("An old version of '{}' was deleted.", accountsFilePath);
}
try (PrintWriter pw = new PrintWriter(csvOutputFile)) {
pw.print(csvString);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("data", accountsFile);
return client.post()
.uri(createAccountsUri())
.header("customHeaderName", "customHeaderValue")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(builder.build()))
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.releaseBody().thenReturn(response.statusCode());
} else {
throw new ServiceException("Error uploading file");
}
});
}
private URI createAccountsUri() {
return fromUriString(apiBaseUrl).path("/gate/account/import/group-accounts").build().toUri();
}
}
I used spring-boot-starter-parent version 2.6.3 for this project.
I've been following asserting-log-messages-with-log4j2-and-mockito to write TestNG test to test logging for Log4j2. Most of what is written in the post seems to work. However, when I'm running my test I'm getting:
Wanted but not invoked:
appender.append(<Capturing argument>);
-> at LoggingTest.test(LoggingTest.java:105)
However, there were exactly 2 interactions with this mock:
appender.getName();
-> at org.apache.logging.log4j.core.config.AbstractConfiguration.addAppender(AbstractConfiguration.java:603)
appender.getName();
-> at org.apache.logging.log4j.core.config.AppenderControl.<init>(AppenderControl.java:51)
My TestNGclass is:
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.mockito.ArgumentCaptor;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class LoggingTest {
private Appender appender;
private ArgumentCaptor<LogEvent> captor;
#BeforeMethod
public void setUp() {
appender = mock(Appender.class);
captor = ArgumentCaptor.forClass(LogEvent.class);
reset(appender);
when(appender.getName()).thenReturn("Appender");
when(appender.isStarted()).thenReturn(true);
when(appender.isStopped()).thenReturn(false);
LoggerContext context = (LoggerContext)LogManager.getContext();
Configuration config = context.getConfiguration();
config.addAppender(appender);
LoggerConfig rootConfig = config.getRootLogger();
rootConfig.setLevel(Level.INFO);
rootConfig.addAppender(appender, Level.INFO, null);
context.updateLoggers();
}
#Test
public void test() {
LogManager.getLogger().info("testing");
verify(appender).append(captor.capture());
LogEvent logEvent = captor.getValue();
assertThat(logEvent.getMessage()).isEqualTo("test");
}
}
I've been looking at this for a while and have not been able to find a my error. Could someone point me in the right direction?
I am using Eclipse Mars 2, maven 3.3.9 and apache-tomee-plus-1.7.4.
I have 2 projects (A and B)
Project A is a Web Service compiled like a WAR using maven 3.3.9 and deployed into TOMEE_HOME/webapps
Project B is an EJB module compiled like a EAR using maven 3.3.9 and deployed into TOMEE_HOME/apps (this project include other project with ejb clasess and compiled like a jar file)
These projects do not depend on each other in the pom.xml but I need to lookup an EJB in project A from project B.
---------- Project B Implementation -----------
Local Bean Interface in project B:
package co.edu.uniquindio.model.ejb.interfaces;
import javax.ejb.Local;
import org.springframework.context.support.ClassPathXmlApplicationContext;
#Local
public interface IReporte {
public Object generate1();
public Object generate2();
public void setContext(ClassPathXmlApplicationContext context);
}
Implement local bean interface in project B:
package co.edu.uniquindio.model.ejb;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import co.edu.uniquindio.model.ejb.interfaces.IReporte;
#Stateless
#EJB(beanInterface = IReporte.class, beanName="ReporteEJB", name="IReporte")
public class ReporteEJB implements IReporte{
private ClassPathXmlApplicationContext context;
#Override
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public Object generate1(){
// do somthing amazing
}
#Override
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public Object generate2(){
// do somthing amazing
}
#Override
public void setContext(ClassPathXmlApplicationContext context) {
this.context = context;
}
}
---------- Project A Implementation -----------
The way that I develop lookup is:
package co.swatit.rest.services;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import co.edu.uniquindio.model.ejb.interfaces.IReporte;
#Path("/ReporteWS")
public class ReporteWS {
#POST
#Path("generate1and2")
#Produces(MediaType.APPLICATION_JSON)
#Consumes({ MediaType.APPLICATION_JSON} )
public Response generate1and2() {
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
try {
Context ctx = new InitialContext(props);
// I do not know if use IReporte or ReporteEJB to cast. and I do not know how to import it.
IReporte ejbLocal = (IReporte) ctx.lookup("java:global/Sac-report-ear-1.0.0/co.swatit-Sac-report-ejb-1.0.0/ReporteEJB");
ejbLocal.generate1();
ejbLocal.generate2();
} catch (Exception exception) {
exception.printStackTrace()
}
return Response.status(Status.OK)
.entity(ejbLocal).build();
}
}
I do not know if it is possible import the local bean in project A to lookup that bean:
import co.edu.uniquindio.model.ejb.interfaces.IReporte
I do not know if use IReporte or ReporteEJB to cast, and I do not know how to import it.
Thank you for your help.
The hibernate validations documentation describes how to create ConstraintMappingContributors here.
It states:
You then need to specify the fully-qualified class name of the
contributor implementation in META-INF/validation.xml, using the
property key hibernate.validator.constraint_mapping_contributors. You
can specify several contributors by separating them with a comma.
Given I have many of these, what would be the most appropriate way to auto-discover these i.e. via #Component and add them dynamically at runtime to the ConstrainMappingConfiguration during Spring Boot startup.
For example.. if a developer creates a new ConstraintMappingContributor, it should be picked up and added automatically when spring boot starts, requiring no other file changes.
This is what I came up with, seems to be working for me.
package...
import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
#Configuration
public class ValidationConfiguration {
private final List<ConstraintMappingContributor> contributors;
public ValidationConfiguration(Optional<List<ConstraintMappingContributor>> contributors) {
this.contributors = contributors.orElseGet(ArrayList::new);
}
#Bean
public LocalValidatorFactoryBean validatorFactory() {
return new ValidatorFactoryBean(this.contributors);
}
}
package...
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import javax.validation.Configuration;
import java.util.List;
public class ValidatorFactoryBean extends LocalValidatorFactoryBean {
private final List<ConstraintMappingContributor> contributors;
ValidatorFactoryBean(List<ConstraintMappingContributor> contributors) {
this.contributors = contributors;
}
#Override
protected void postProcessConfiguration(Configuration<?> cfg) {
if (cfg instanceof HibernateValidatorConfiguration) {
HibernateValidatorConfiguration configuration = (HibernateValidatorConfiguration) cfg;
this.contributors.forEach(contributor -> contributor.createConstraintMappings(() -> {
DefaultConstraintMapping mapping = new DefaultConstraintMapping();
configuration.addMapping(mapping);
return mapping;
}));
}
}
}
I invoke it like this...
if(SpringValidatorAdapter.class.isInstance(this.validatorFactory)){
SpringValidatorAdapter.class.cast(this.validatorFactory).validate(entity, errors);
}
I'm trying to configure the LogbackValve for getting access logs in case my Spring Boot based web application is running from embedded Tomcat. Following is the code for configuration:
import javax.servlet.Servlet;
import org.apache.catalina.startup.Tomcat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ch.qos.logback.access.tomcat.LogbackValve;
#Configuration
public class EmbeddedTomcatConfigurator {
#Bean
#ConditionalOnClass({ Servlet.class, Tomcat.class })
#ConditionalOnBean(value = LogbackValve.class)
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory(LogbackValve logbackValve) {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addContextValves(logbackValve);
return factory;
}
#Bean
#ConditionalOnProperty(name = "embedded.tomcat.logback.access.config.path")
public LogbackValve logbackValve(#Value("${embedded.tomcat.logback.access.config.path:}") String fileName) {
LogbackValve logbackValve = new LogbackValve();
logbackValve.setFilename(fileName);
return logbackValve;
}
}
However, everytime I start the application using "mvn spring-boot:run" in debug mode, I see logs saying, "LogbackValve not found" when trying to create instance of "tomcatEmbeddedServletContainerFactory" bean. However, another log statement indicates creation of this bean. Due to this, it always initializes the bean defined in the auto-configuration class "org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration".
For now, I've modified my class as :
import javax.servlet.Servlet;
import org.apache.catalina.startup.Tomcat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ch.qos.logback.access.tomcat.LogbackValve;
#Configuration
public class EmbeddedTomcatConfigurator {
#Bean
#ConditionalOnClass({ Servlet.class, Tomcat.class })
#ConditionalOnProperty(name = "embedded.tomcat.logback.access.config.path")
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory(#Value("${embedded.tomcat.logback.access.config.path:}") String logbackAccessPath) {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addContextValves(getLogbackValve(logbackAccessPath));
return factory;
}
private LogbackValve getLogbackValve(String fileName) {
LogbackValve logbackValve = new LogbackValve();
logbackValve.setFilename(fileName);
return logbackValve;
}
}
I've already asked this question on Git and it has been resolved. But, here, the point I'm trying to bring up is, why the #ConditionalOnBean(value = LogbackValve.class) isn't detecting the bean, which has been defined as well.