Spring boot custom Servlet doesn't map to bean name - spring

I am trying to register a custom servlet.
I used this code in a #Configuration class:
#Bean (name="probe")
public PingServlet probe(){
return new PingServlet();
}
I thought this would be mapped to /probe, but it doesn't. I maps to '/' and the reason is that in class ServletContextInitializerBeans, there this method:
private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory,
Class<T> type, Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
List<Map.Entry<String, B>> beans = getOrderedBeansOfType(beanFactory, beanType,
this.seen);
for (Entry<String, B> bean : beans) {
if (this.seen.add(bean.getValue())) {
int order = getOrder(bean.getValue());
String beanName = bean.getKey();
// One that we haven't already seen
RegistrationBean registration = adapter.createRegistrationBean(beanName,
bean.getValue(), beans.size());
registration.setName(beanName);
registration.setOrder(order);
this.initializers.add(type, registration);
if (this.log.isDebugEnabled()) {
this.log.debug(
"Created " + type.getSimpleName() + " initializer for bean '"
+ beanName + "'; order=" + order + ", resource="
+ getResourceDescription(beanName, beanFactory));
}
}
}
}
The line List<Map.Entry<String, B>> beans = getOrderedBeansOfType(beanFactory, beanType, this.seen);, return list of 1 bean only (my servlet) although beanType is javax Servlet and I would expect DispatcherServlet to be there as well (I'm also using Spring MVC).
This results to an error in the following method (in class ServletRegistrationBeanAdapter):
#Override
public RegistrationBean createRegistrationBean(String name, Servlet source,
int totalNumberOfSourceBeans) {
String url = (totalNumberOfSourceBeans == 1 ? "/" : "/" + name + "/");
if (name.equals(DISPATCHER_SERVLET_NAME)) {
url = "/"; // always map the main dispatcherServlet to "/"
}
ServletRegistrationBean bean = new ServletRegistrationBean(source, url);
bean.setMultipartConfig(this.multipartConfig);
return bean;
}
Since the beans list is of size 1, in the createRegistrationBean it hard codes the mapping to '/'.
This in turn causes they embedded jetty to fail starting as there are 2 mappings to '/' (DispatcherServlet and my PingServlet).
Any ideas what's going wrong here?

Thanks to #M. Deinum This works:
#Bean
public ServletRegistrationBean pingRegistration(PingServlet pingServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
pingServlet);
registration.addUrlMappings("/probe/*");
return registration;
}

Related

Spring Boot: The method add(Converter) in the type Set<Converter> is not applicable for the arguments (...)

When trying to add something to the converters HashSet in ConversionConfig.java, I get the following error in RED:
Error:
The method add(Converter) in the type Set<Converter> is not applicable for the arguments (RoomEntityToReservationResponseConverter)
ConversionConfig.java:
#Configuration
public class ConversionConfig {
#SuppressWarnings("rawtypes")
private Set<Converter> getConverters() {
Set<Converter> converters = new HashSet<Converter>();
converters.add(new RoomEntityToReservationResponseConverter());
return converters;
}
#Bean public ConversionService conversionService() {
ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
bean.setConverters(getConverters());
bean.afterPropertiesSet();
return bean.getObject();
}
}
RoomEntityToReservationResponseConverter.java:
public class RoomEntityToReservationResponseConverter implements Converter<RoomEntity, ReservationResponse>{
#Override
public ReservationResponse convert(RoomEntity source) {
ReservationResponse reservationResponse = new ReservationResponse();
reservationResponse.setRoomNumber(source.getRoomNumber());
reservationResponse.setPrice( Integer.valueOf(source.getPrice()) );
Links links = new Links();
Self self = new Self();
self.setRef(ResourceConstants.ROOM_RESERVATION_V1 + "/" + source.getId());
links.setSelf(self);
reservationResponse.setLinks(links);
return reservationResponse;
}
}
Not sure what is going on - I am new to Spring Boot. Looking at similar questions has not helped, because I don't understand the root problem, if someone could spell out the solution using code from this particular instance, that would be helpful to get a better idea.

How to set Max Pagination Size in Spring Boot?

I'm developing Spring Boot + Spring Data Mongo + Spring HATEOAS example. I'm using Spring Boot V2.2.2.RELEASE.
I'm globally trying to set the Pagination PageSize limit to 200. For that I went through spring data jpa limit pagesize, how to set to maxSize and configurations like below
# DATA WEB (SpringDataWebProperties)
spring.data.web.pageable.default-page-size=20 # Default page size.
spring.data.web.pageable.max-page-size=2000 # Maximum page size to be accepted.
spring.data.web.pageable.one-indexed-parameters=false # Whether to expose and assume 1-based page number indexes.
spring.data.web.pageable.page-parameter=page # Page index parameter name.
spring.data.web.pageable.prefix= # General prefix to be prepended to the page number and page size parameters.
spring.data.web.pageable.qualifier-delimiter=_ # Delimiter to be used between the qualifier and the actual page number and size properties.
spring.data.web.pageable.size-parameter=size # Page size parameter name.
spring.data.web.sort.sort-parameter=sort # Sort parameter name.
If Client sends more than 200 PageSize, then I need to show user friendly error message.
#Configuration
public class PaginationConfig extends SpringDataWebConfiguration{
public PaginationConfig(ApplicationContext context, ObjectFactory<ConversionService> conversionService) {
super(context, conversionService);
}
#Bean
public PageableHandlerMethodArgumentResolver pageableResolver() {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver(sortResolver());
resolver.setMaxPageSize(1000);
return resolver;
}
}
I used above configurations, but getting below error.
2020-02-06 21:08:31.963 ERROR [Reference Data,,,] 22812 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Field pagedAssembler in com.example.EmployeeController required a bean of type 'org.springframework.data.web.PagedResourcesAssembler' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.springframework.data.web.PagedResourcesAssembler' in your configuration.
Code:
#GetMapping(value = "/countries", produces = { MediaType.APPLICATION_JSON })
public ResponseEntity<PagedModel<EmployeeModel>> findEmployees(#Parameter(hidden=true) Pageable pageable) {
Page<EmployeeDto> page = EmployeeService.findAllEmployees(page_params, pageable);
PagedModel<EmployeeModel> model = pagedAssembler.toModel(page, EmployeeAssembler);
return new ResponseEntity<>(model, HttpStatus.OK);
}
I was able to solve the issue using below code.
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver() {
#Override
public Pageable resolveArgument(MethodParameter methodParameter,
#Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
#Nullable WebDataBinderFactory binderFactory) {
Pageable p = super.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);
if(p.getPageSize() > Integer.parseInt(500)) {
throw new MandatoryResourceException("Error"), env.getProperty("500"));
}
return p;
}
};
resolvers.add(resolver);
WebMvcConfigurer.super.addArgumentResolvers(resolvers);
}
}

Getting Class annotation for a given Spring Bean

I have two custom annotation as described below.
CustomAnnotationMain is a Spring Component based annotation.
CustomAnnotationChild is a Spring Bean based annotation.
Below is the code snippet which uses the 2 custom annotations.
#CustomAnnotationMain(value = "parent")
public class MainClass{
#CustomAnnotationChild(value = "child1")
public ObjectBuilder getObject1() {
// logic
}
#CustomAnnotationChild(value = "child2")
public ObjectBuilder getObject2() {
// logic
}
}
Question: How can I get the list of all CustomAnnotationMain annotated classes and also all the beans + annotation infos that are available as part of the component?
I did the following to get all the beans annotated with #CustomAnnotationChild. But I am not sure how to access the class in which the bean is available. I need to access #CustomAnnotationMain for a given bean.
allBuilders = context.getBeansOfType(ObjectBuilder.class);
PS: This is not Spring Boot based project. I use only the spring core libs.
I did something similar. Introduced an interface Proxyable and need to find all the beans annotated with the interface or create proxy s for all defined interfaces.
https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/service/ProxyableScanRegistrar.java
In your case you should replace Proxyable with your CustomAnnotationMain.
The logic of ClassPathScanningCandidateComponentProvider definition can be changed to reflect your filter (I need there interfaces only).
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LOG.debug("Registering #Proxyable beans");
// Get the ProxyableScan annotation attributes
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ProxyableScan.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("value");
if (basePackages.length == 0) {
// If value attribute is not set, fallback to the package of the annotated class
basePackages = new String[]{((StandardAnnotationMetadata) metadata).getIntrospectedClass().getPackage().getName()};
}
// using these packages, scan for interface annotated with Proxyable
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment) {
// Override isCandidateComponent to only scan for interface
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isIndependent() && metadata.isInterface();
}
};
provider.addIncludeFilter(new AnnotationTypeFilter(Proxyable.class));
ControllerFactory factory = getControllerFactory((DefaultListableBeanFactory) registry);
// Scan all packages
for (String basePackage : basePackages) {
for (BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage)) {
try {
Class c = this.getClass().getClassLoader().loadClass(beanDefinition.getBeanClassName());
if (!hasImplementingClass(c, basePackages)) {
//creating missing beans logic is skipped
}
} catch (ClassNotFoundException e) {
throw new SOAControllerCreationException("cannot create proxy for " + beanDefinition.getBeanClassName());
}
}
}
}
}
Hope it helps

Spring ws - Datahandler with Swaref still null

I used the Spring boot starter web services to develop a SOAP with attachment service.
For an unknown reason attachments aren't unmarshalled.. Jaxb Unmarshaller is used but the property AttachmentUnmarshaller inside is "null" ...so probably the reason why DataHandler unmarshalling isn't done ??
As in a JEE environment the attachmentUnmarshaller is handle by jaxws .. how configure it in a standalone process like spring boot on tomcat ??
Java version : 8_0_191
Spring boot version : 2.1
I faced similar issue, but with marshalling.
Jaxb2Marshaller has its own implementations of AttachmentMarshaller and AttachmentUnarshaller. But for these to work, mtomEnabled property should be set to true. If it's not, defaults will be used, which are not instantiated.
Try setting setMtomEnabled(true) on your Jaxb2Marshaller.
This will probably solve your issue.
For people, who encounter same issue with marshalling - it's a bit more complicated. Jaxb2 AttachmentMarshaller is not correctly implemented as per WS-I Attachment Profile 1.0 - http://www.ws-i.org/Profiles/AttachmentsProfile-1.0.html#Example_Attachment_Description_Using_swaRef
You will have to override marshalling behavior of Jaxb2Marshaller then.
Notice: this solution assumes that MTOM is always disabled.
#Configuration
class SOAPConfiguration {
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller() {
#Override
public void marshal(Object graph, Result result, #Nullable MimeContainer mimeContainer) throws XmlMappingException {
try {
javax.xml.bind.Marshaller marshaller = createMarshaller();
if (mimeContainer != null) {
marshaller.setAttachmentMarshaller(
new SwaRefAttachmentMarshaller(mimeContainer)
);
marshaller.marshal(graph, result);
} else {
super.marshal(graph, result, null);
}
} catch (JAXBException ex) {
throw convertJaxbException(ex);
}
}
};
marshaller.setPackagesToScan("my.package");
marshaller.setMtomEnabled(false);
return marshaller;
}
private class SwaRefAttachmentMarshaller extends AttachmentMarshaller {
private final MimeContainer mimeContainer;
private SwaRefAttachmentMarshaller(MimeContainer mimeContainer) {
this.mimeContainer = mimeContainer;
}
#Override
public String addMtomAttachment(DataHandler data, String elementNamespace, String elementLocalName) {
return null;
}
#Override
public String addMtomAttachment(byte[] data, int offset, int length, String mimeType, String elementNamespace, String elementLocalName) {
return null;
}
#Override
public String addSwaRefAttachment(DataHandler data) {
String attachmentId = UUID.randomUUID().toString();
mimeContainer.addAttachment("<" + attachmentId + ">", data);
return "cid:" + attachmentId;
}
}
}

Automatically register XA Resource Spring Boot

I'm trying to implement XA transactions in my Spring Boot app across Hazelcast and JPA persisting to PostgreSQL. Putting the Atomikos Spring Boot starter in my pom.xml got it to load the JtaTransactionManager to be used with the #Transactional annotations, but the Hazelcast XA Resource is not being enlisted with the transaction.
How do I get Spring Boot to automatically enlist my XA Resources with the JTA UserTransaction as part of the AOP transaction interceptor that's using the JtaTransactionManager?
I solved this by using an annotation and AspectJ Aspect as described here. Also see this for defining the pointcuts to match either class or method level annotations, and you may need to do this:
#EnableTransactionManagement(order = Ordered.HIGHEST_PRECEDENCE)
to have the transaction interceptor happen before this code is called.
#Aspect
#Component
public class XAResourceAspect {
#Autowired
JtaTransactionManager jtaTransactionManager;
#Autowired
ApplicationContext applicationContext;
#Pointcut("within(#XAResource *)")
public void beanAnnotatedWithAnnotation() {}
#Pointcut("execution(public * *(..))")
public void publicMethod() {}
#Pointcut("publicMethod() && beanAnnotatedWithAnnotation()")
public void publicMethodInsideAnnotatedClass() {}
private ThreadLocal<Map<Transaction, Set<String>>> enlistedResources = new ThreadLocal<>();
#Around("#annotation(ppi.nestup.v3.annotation.XAResource) || publicMethodInsideAnnotatedClass()")
public Object enlistResources(ProceedingJoinPoint joinPoint) throws Throwable {
boolean setThreadLocal = false;
Transaction transaction = jtaTransactionManager.getTransactionManager().getTransaction();
if (transaction != null) {
Map<Transaction, Set<String>> transactionMap = enlistedResources.get();
LOG.info("Enlisting resources for joinpoint " + joinPoint + " and transaction " + transaction);
if (transactionMap == null) {
transactionMap = new HashMap<>();
enlistedResources.set(transactionMap);
setThreadLocal = true;
LOG.info("Created new ThreadLocal for transaction " + transaction);
} else {
LOG.info("Found existing ThreadLocal " + transactionMap);
}
transactionMap.computeIfAbsent(transaction, k -> new HashSet<>());
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Class withinType = joinPoint.getSourceLocation().getWithinType();
XAResource annotation = method.getAnnotation(XAResource.class);
if (annotation == null) {
annotation = (XAResource) withinType.getAnnotation(XAResource.class);
}
String[] resourceNames = annotation.value();
for (String name : resourceNames) {
if (!transactionMap.get(transaction).contains(name)) {
javax.transaction.xa.XAResource resource =
(javax.transaction.xa.XAResource) applicationContext.getBean(name);
try {
transaction.enlistResource(resource);
} catch (IllegalStateException e) {
LOG.error("Caught exception trying to enlist resource " + name + " for transaction " + transaction + " and joinpoint " + joinPoint);
e.printStackTrace();
}
transactionMap.get(transaction).add(name);
}
}
}
Object proceed = joinPoint.proceed();
if (setThreadLocal) {
LOG.info("Removing threadlocal");
enlistedResources.remove();
}
return proceed;
}
}
I haven't done a lot of testing of this yet, but it's working so far.

Resources