Mule not displaying/reading custom messages from ReloadableResourceBundles - spring

I can't figure out how to get my custom message from the Spring Errors object. It seems like I have stuff wired up properly. I've seen examples where the default message is actually the custom message, but that seems a hack and contrary to the documentation.
As I understand it, using ValidationUtils, I could have the following:
ValidationUtils.rejectIfEmpty(errors, "myfield", "myfield.required", "Column 'myfield' is required.");
I also think I understand that the second parameter "myfield.required" is in essence the key to a properties file entry.
So, I have this properties file sitting my src/main/resouces folder named validation-messages_en_US.properties with this entry:
myfield.required=Column 'myfield' is a custom message !
My Spring Bean definition:
<spring:beans>
<context:component-scan base-package="com.mycompany.myproject" />
<spring:bean id="messageSource" name="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<spring:property name="basenames" value="classpath:validation-messages"/>
</spring:bean>
</spring:beans>
My Validator:
import java.util.List;
import org.apache.log4j.Logger;
import org.mule.api.annotations.param.Payload;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.mycompany.myproject.vsd.request.VSDRequest;
#Component
public class VSDRequestValidator implements Validator {
private static final Logger logger = Logger.getLogger(VSDRequestValidator.class);
#Autowired
private MessageSource messageSource;
#Override
public boolean supports(Class<?> clazz) {
return VSDRequest.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object object, Errors errors) {
VSDRequest request = (VSDRequest) object;
ValidationUtils.rejectIfEmpty(errors, "myfield", "myfield.required", "Column 'myfield' is required.");
if (errors.hasErrors()) {
List<ObjectError> objErrors = errors.getAllErrors();
for (ObjectError e : objErrors) {
logger.info(e.getDefaultMessage());
}
}
}
/**
* Receive <code>payload</code> to perform validations on.
*
* #param object
*/
public void validate(#Payload Object object) {
if (object == null) {
throw new IllegalArgumentException("The supplied [Validator] is required and must not be null.");
}
if (!supports(object.getClass())) {
throw new IllegalArgumentException("The supplied [Validator] must support the validation of [VSDRequest] instances.");
}
BeanPropertyBindingResult errors = new BeanPropertyBindingResult((VSDRequest) object, "VSDRequest");
logger.info(messageSource.getMessage("myfield.required", null, null)); // This get the message I want
validate(object, errors);
}
}
My MessageSource properly injects. I injected it into my class just to check it and know it's being recognized. I shouldn't even need to do that if Spring is reading the ReloadableResourceBundle.
Does the getDefaultMessage() return either the custom message or the default message? Maybe I'm looking at this all wrong?

Related

Spring - micrometer + opentelemetry-exporter-otlp

Is it possible to configure micrometer to send traces to otel container in spring?
I easily configured sending spans to Zipkin and Wavefront:
https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/reference/htmlsingle/#actuator.micrometer-tracing.tracers but there is nothing about exporting to Otel.
Micrometer documentation also does not mention about exporting spans to otel container https://micrometer.io/docs/tracing#_using_micrometer_tracing_directly
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.trace.export.SpanExporter;
/**
* As of SpringBoot 3.0.2 the inclusion of io.micrometer:micrometer-tracing-bridge-otel and
* io.opentelemetry:opentelemetry-exporter-otlp is not sufficient to bootstrap the SpanExporter. Adding
* io.opentelemetry:opentelemetry-sdk-extension-autoconfigure also does not help. Hence this solution which will
* probably be redundant one day.
*/
#Configuration
public class OpenTelemetryConfig {
#Value("${otel.exporter.otlp.traces.endpoint:http://localhost:4317}")
private String tracesEndpoint;
#Bean
public SpanExporter spanExporter() {
return OtlpGrpcSpanExporter.builder().setEndpoint(tracesEndpoint).build();
}
}
I also found the following necessary if you want to use io.opentelemetry.instrumentation:opentelemetry-jdbc (and probably others) as it relies on GlobalOpenTelemetry.get(). This is forcing it to be the instance produced by the micrometer-tracing-bridge-otel.
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
#Configuration
public class GlobalOpenTelemetrySetter implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof OpenTelemetry openTelemetry) {
GlobalOpenTelemetry.set(openTelemetry);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
I worry that this could have startup race conditions but is working for me at the moment. I hope the Spring team can provide proper clarification at some point.

how do I throw a error on unknown fields in json request to spring restapi

I have a spring rest api which gets json data and binds to a pojo GetData.
Whenever i recieve unknown fields it doesnt fail or throw any exception. My requirement here is it should throw a error when it receives unknown fields in json data.
public ResponseEntity<Error> saveLocation(#Valid #RequestBody GetData getdata,BindingResult bindingResults) {
Below is my Pojo GetData
public class GetData{
#JsonProperty("deviceID")
#Pattern(regexp="^[\\p{Alnum}][-\\p{Alnum}\\p{L}]+[\\p{Alnum}]$",message = "Not a valid Device Id")
private String deviceID;
#JsonProperty("Coordinates")
#Pattern(regexp="^[\\p{Alnum}\\-][\\.\\,\\-\\_\\p{Alnum}\\p{L}\\s]+|",message = "Coordinates are not valid")
private String coordinates;}
Below is my json request.
{
"deviceID" : "01dbd619-843b-4197-b954",
"Coordinates" : "12.984012,80.246712",
}
Now if i send a request with an extra field say country. It doesn't throw any error.
{
"deviceID" : "01dbd619-843b-4197-b954",
"Coordinates" : "12.984012,80.246712",
"country" : "dsasa"
}
Please suggest how can i have an error for unknown properties being sent in a json request
You can try out any one of the below implementations, it works for me. You will have to override one more method from ResponseEntityExceptionHandler or by using ExceptionHandler.
1. By Overriding Method of ResponseEntityExceptionHandler
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(CustomExceptionHandler.class);
//Other Handlers
// Handle 400 Bad Request Exceptions
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
log.info(ex.getLocalizedMessage() + " ",ex);
final CustomErrorMessage errorMessage = new CustomErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, ex.fillInStackTrace().toString());
return handleExceptionInternal(ex, errorMessage, headers, errorMessage.getStatus(), request);
}
//Other Handlers
}
Apart from above implementation you can try out the below one also, if you want to throw error only if unrecognised properties are present in request payload or empty property and empty value is present like below JSON
{
"":""
}
2. Using ExceptionHandler
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class GenericExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GenericExceptionHandler.class);
#ExceptionHandler(value = {UnrecognizedPropertyException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleUnrecognizedPropertyException(UnrecognizedPropertyException ex) {
log.info(ex.getLocalizedMessage() + " ",ex);
final String error = "JSON parse error: Unrecognized field " + "[ " + ex.getPropertyName() + " ]";
final CustomErrorMessage errorMessage = new CustomErrorMessage(error, InfoType.ERROR, HttpStatus.BAD_REQUEST);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorMessage);
}
}
Note : For above both implementations to work properly, you need to add the below line in your application.properties file.
spring.jackson.deserialization.fail-on-unknown-properties=true
Hope this will help you :)
You need to configure your ObjectMapper to handle such cases:
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
Alternatively you can use:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = false)
public class GetData {
}

Spring beans are not injected in flyway java based migration

I'm trying to inject component of configuration properties in the flyway migration java code but it always null.
I'm using spring boot with Flyway.
#Component
#ConfigurationProperties(prefix = "code")
public class CodesProp {
private String codePath;
}
Then inside Flyway migration code, trying to autowrire this component as following:
public class V1_4__Migrate_codes_metadata implements SpringJdbcMigration {
#Autowired
private CodesProp codesProp ;
public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
codesProp.getCodePath();
}
Here, codesProp is always null.
Is there any way to inject spring beans inside flyway or make it initialized before flyway bean?
Thank You.
Flyway doesn't support dependency injection into SpringJdbcMigration implementations. It simply looks for classes on the classpath that implement SpringJdbcMigration and creates a new instance using the default constructor. This is performed in SpringJdbcMigrationResolver. When the migration is executed, SpringJdbcMigrationExecutor creates a new JdbcTemplate and then calls your migration implementation's migrate method.
If you really need dependencies to be injected into your Java-based migrations, I think you'll have to implement your own MigrationResolver that retrieves beans of a particular type from the application context and creates and returns a ResolvedMigration instance for each.
If like me, you don't want to wait for Flyway 4.1, you can use Flyway 4.0 and add the following to your Spring Boot application:
1) Create a ApplicationContextAwareSpringJdbcMigrationResolver class in your project:
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationType;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.configuration.FlywayConfiguration;
import org.flywaydb.core.api.migration.MigrationChecksumProvider;
import org.flywaydb.core.api.migration.MigrationInfoProvider;
import org.flywaydb.core.api.migration.spring.SpringJdbcMigration;
import org.flywaydb.core.api.resolver.ResolvedMigration;
import org.flywaydb.core.internal.resolver.MigrationInfoHelper;
import org.flywaydb.core.internal.resolver.ResolvedMigrationComparator;
import org.flywaydb.core.internal.resolver.ResolvedMigrationImpl;
import org.flywaydb.core.internal.resolver.spring.SpringJdbcMigrationExecutor;
import org.flywaydb.core.internal.resolver.spring.SpringJdbcMigrationResolver;
import org.flywaydb.core.internal.util.ClassUtils;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.Pair;
import org.flywaydb.core.internal.util.StringUtils;
import org.flywaydb.core.internal.util.scanner.Scanner;
import org.springframework.context.ApplicationContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
* Migration resolver for {#link SpringJdbcMigration}s which are registered in the given {#link ApplicationContext}.
* This resolver provides the ability to use other beans registered in the {#link ApplicationContext} and reference
* them via Spring's dependency injection facility inside the {#link SpringJdbcMigration}s.
*/
public class ApplicationContextAwareSpringJdbcMigrationResolver extends SpringJdbcMigrationResolver {
private final ApplicationContext applicationContext;
public ApplicationContextAwareSpringJdbcMigrationResolver(Scanner scanner, Location location, FlywayConfiguration configuration, ApplicationContext applicationContext) {
super(scanner, location, configuration);
this.applicationContext = applicationContext;
}
#SuppressWarnings("unchecked")
#Override
public Collection<ResolvedMigration> resolveMigrations() {
// get all beans of type SpringJdbcMigration from the application context
Map<String, SpringJdbcMigration> springJdbcMigrationBeans =
(Map<String, SpringJdbcMigration>) this.applicationContext.getBeansOfType(SpringJdbcMigration.class);
ArrayList<ResolvedMigration> resolvedMigrations = new ArrayList<ResolvedMigration>();
// resolve the migration and populate it with the migration info
for (SpringJdbcMigration springJdbcMigrationBean : springJdbcMigrationBeans.values()) {
ResolvedMigrationImpl resolvedMigration = extractMigrationInfo(springJdbcMigrationBean);
resolvedMigration.setPhysicalLocation(ClassUtils.getLocationOnDisk(springJdbcMigrationBean.getClass()));
resolvedMigration.setExecutor(new SpringJdbcMigrationExecutor(springJdbcMigrationBean));
resolvedMigrations.add(resolvedMigration);
}
Collections.sort(resolvedMigrations, new ResolvedMigrationComparator());
return resolvedMigrations;
}
ResolvedMigrationImpl extractMigrationInfo(SpringJdbcMigration springJdbcMigration) {
Integer checksum = null;
if (springJdbcMigration instanceof MigrationChecksumProvider) {
MigrationChecksumProvider version = (MigrationChecksumProvider) springJdbcMigration;
checksum = version.getChecksum();
}
String description;
MigrationVersion version1;
if (springJdbcMigration instanceof MigrationInfoProvider) {
MigrationInfoProvider resolvedMigration = (MigrationInfoProvider) springJdbcMigration;
version1 = resolvedMigration.getVersion();
description = resolvedMigration.getDescription();
if (!StringUtils.hasText(description)) {
throw new FlywayException("Missing description for migration " + version1);
}
} else {
String resolvedMigration1 = ClassUtils.getShortName(springJdbcMigration.getClass());
if (!resolvedMigration1.startsWith("V") && !resolvedMigration1.startsWith("R")) {
throw new FlywayException("Invalid Jdbc migration class name: " + springJdbcMigration.getClass()
.getName() + " => ensure it starts with V or R," + " or implement org.flywaydb.core.api.migration.MigrationInfoProvider for non-default naming");
}
String prefix = resolvedMigration1.substring(0, 1);
Pair info = MigrationInfoHelper.extractVersionAndDescription(resolvedMigration1, prefix, "__", "");
version1 = (MigrationVersion) info.getLeft();
description = (String) info.getRight();
}
ResolvedMigrationImpl resolvedMigration2 = new ResolvedMigrationImpl();
resolvedMigration2.setVersion(version1);
resolvedMigration2.setDescription(description);
resolvedMigration2.setScript(springJdbcMigration.getClass().getName());
resolvedMigration2.setChecksum(checksum);
resolvedMigration2.setType(MigrationType.SPRING_JDBC);
return resolvedMigration2;
}
}
2) Add a new configuration class to post process the Spring Boot generated Flyway instance:
import org.flywaydb.core.Flyway;
import org.flywaydb.core.internal.dbsupport.DbSupport;
import org.flywaydb.core.internal.dbsupport.h2.H2DbSupport;
import org.flywaydb.core.internal.dbsupport.mysql.MySQLDbSupport;
import com.pegusapps.zebra.infrastructure.repository.flyway.ApplicationContextAwareSpringJdbcMigrationResolver;
import org.flywaydb.core.internal.resolver.sql.SqlMigrationResolver;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.PlaceholderReplacer;
import org.flywaydb.core.internal.util.scanner.Scanner;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.sql.SQLException;
#Configuration
#ComponentScan("db.migration")
public class FlywayConfiguration {
#Bean
public BeanPostProcessor postProcessFlyway(ApplicationContext context) {
return new BeanPostProcessor() {
#Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
return o;
}
#Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
if (o instanceof Flyway) {
Flyway flyway = (Flyway) o;
flyway.setSkipDefaultResolvers(true);
ApplicationContextAwareSpringJdbcMigrationResolver resolver = new ApplicationContextAwareSpringJdbcMigrationResolver(
new Scanner(Thread.currentThread().getContextClassLoader()),
new Location("classpath:db/migration"),
context.getBean(org.flywaydb.core.api.configuration.FlywayConfiguration.class),
context);
SqlMigrationResolver sqlMigrationResolver = null;
try {
sqlMigrationResolver = new SqlMigrationResolver(
getDbSupport(),
new Scanner(Thread.currentThread().getContextClassLoader()),
new Location("classpath:db/migration"),
PlaceholderReplacer.NO_PLACEHOLDERS,
"UTF-8",
"V",
"R",
"__",
".sql");
} catch (SQLException e) {
e.printStackTrace();
}
flyway.setResolvers(sqlMigrationResolver, resolver);
}
return o;
}
private DbSupport getDbSupport() throws SQLException {
DataSource dataSource = context.getBean(DataSource.class);
if( ((org.apache.tomcat.jdbc.pool.DataSource)dataSource).getDriverClassName().equals("org.h2.Driver"))
{
return new H2DbSupport(dataSource.getConnection());
}
else
{
return new MySQLDbSupport(dataSource.getConnection());
}
}
};
}
}
Note that I have some hardcoded dependencies on tomcat jdbc pool, h2 and mysql. If you are using something else, you will need to change the code there (If there is anybody that knows how to avoid it, please comment!)
Also note that the #ComponentScan package needs to match with where you will put the Java migration classes.
Also note that I had to add the SqlMigrationResolver back in since I want to support both the SQL and the Java flavor of the migrations.
3) Create a Java class in the db.migrations package that does the actual migration:
#Component
public class V2__add_default_surveys implements SpringJdbcMigration {
private final SurveyRepository surveyRepository;
#Autowired
public V2__add_surveys(SurveyRepository surveyRepository) {
this.surveyRepository = surveyRepository;
}
#Override
public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
surveyRepository.save(...);
}
}
Note that you need to make the class a #Component and it needs to implement the SpringJdbcMigration. In this class, you can use Spring constructor injection for any Spring bean from your context you might need to do the migration(s).
Note: Be sure to disable ddl validation of Hibernate, because the validation seems to run before Flyway runs:
spring.jpa.hibernate.ddl-auto=none
In short do not autowire beans in your db migrations or even reference classes from your application!
If you refactor/delete/change classes you referenced in the migration it may not even compile or worse corrupt your migrations.
The overhead of using plain JDBC template for the migrations is not worth the risk.
If you are using deltaspike you can use BeanProvider to get a reference to your Class. Here is a DAO example, but it should work fine with your class too.
Change your DAO code:
public static UserDao getInstance() {
return BeanProvider.getContextualReference(UserDao.class, false, new DaoLiteral());
}
Then in your migration method:
UserDao userdao = UserDao.getInstance();
And there you've got your reference.
(referenced from: Flyway Migration with java)

Customizing PropertyEditorSupport in Spring

I'm working with Spring 3.2. In order to validate double values globally, I use CustomNumberEditor. The validation is indeed performed.
But when I input a number like 1234aaa, 123aa45 and so forth, I expect the NumberFormatException to be thrown but it doesn't. The docs says,
ParseException is caused, if the beginning of the specified string cannot be
parsed
Therefore, such values as mentioned above are parsed up to they are represented as numbers and the rest of the string is then omitted.
To avoid this, and to make it throw an exception, when such values are fed, I need to implement my own Property Editor by extending the PropertyEditorSupport class as mentioned in this question.
package numeric.format;
import java.beans.PropertyEditorSupport;
public final class StrictNumericFormat extends PropertyEditorSupport
{
#Override
public String getAsText()
{
System.out.println("value = "+this.getValue());
return ((Number)this.getValue()).toString();
}
#Override
public void setAsText(String text) throws IllegalArgumentException
{
System.out.println("value = "+text);
super.setValue(Double.parseDouble(text));
}
}
The editors I have specified inside a method annotated with the #InitBinder annotation are as follows.
package spring.databinder;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.context.request.WebRequest;
#ControllerAdvice
public final class GlobalDataBinder
{
#InitBinder
public void initBinder(WebDataBinder binder, WebRequest request)
{
DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
dateFormat.setLenient(false);
binder.setIgnoreInvalidFields(true);
binder.setIgnoreUnknownFields(true);
//binder.setAllowedFields("startDate");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
//The following is the CustomNumberEditor
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setGroupingUsed(false);
binder.registerCustomEditor(Double.class, new CustomNumberEditor(Double.class, numberFormat, false));
}
}
Since I'm using Spring 3.2, I can take advantage of #ControllerAdvice
Out of curiosity, the overridden methods from the PropertyEditorSupport class in the StrictNumericFormat class are never invoked and the statements that redirect the output to the console as specified inside of those methods (getAsText() and setAsText()) don't print anything on the server console.
I have tried all the approaches described in all the answers of that question but none worked for me. What am I missing here? Is this required to configure in some xml file(s)?
Clearly you have nowhere passed the StrictNumericFormat reference. You should register your editor like:
binder.registerCustomEditor(Double.class, new StrictNumericFormat());
BTW Spring 3.X introduced a new way achieving conversion:Converters

#Autowired does not work with my SOAP handler class. Throws a nullPointerException?

This is my SOAP Handler class to generate security service handlers for a CRM. Everything was working fine as I hard coded my credentials - Username & Password. Now I tried to remove the hard-coding by defining the credentials in a properties file and autowiring it in this class. This method is not working and Spring throws a NullPointerExc (autowiring not happening I guess!) everytime I try to access my CRM. Why does #Autowired not work here while it works perfectly well my #Service, #Controller classes? Here is my code:
package com.myPortlet.crmService;
import java.util.Properties;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class ECMClientHeaderHandler implements SOAPHandler<SOAPMessageContext> {
final static Logger logger = LoggerFactory
.getLogger(ECMClientHeaderHandler.class);
private static final String AUTH_NS = "http://schemas.xmlsoap.org/ws/2002/12/secext";
private static final String AUTH_PREFIX = "wss";
public ECMClientHeaderHandler() {
}
public boolean handleFault(SOAPMessageContext smc) {
return true;
}
public void close(MessageContext mc) {
}
#Autowired
private Properties applicationProperties;
public boolean handleMessage(SOAPMessageContext smc) {
boolean direction = ((Boolean) smc
.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY))
.booleanValue();
String userName = applicationProperties.getProperty("myCRM.userName"); /*previously hard-coded*/
String password = applicationProperties.getProperty("myCRM.password"); /*previously hard-coded*/
logger.info("This is USERNAME:"+ userName);
logger.info("This is PASSWORD:"+ password);
if (direction) {
try {
SOAPEnvelope envelope = smc.getMessage().getSOAPPart()
.getEnvelope();
SOAPFactory soapFactory = SOAPFactory.newInstance();
// WSSecurity <Security> header
SOAPElement wsSecHeaderElm = soapFactory.createElement(
"Security", AUTH_PREFIX, AUTH_NS);
SOAPElement userNameTokenElm = soapFactory.createElement(
"UsernameToken", AUTH_PREFIX, AUTH_NS);
SOAPElement userNameElm = soapFactory.createElement("Username",
AUTH_PREFIX, AUTH_NS);
userNameElm.addTextNode(userName);
SOAPElement passwdElm = soapFactory.createElement("Password",
AUTH_PREFIX, AUTH_NS);
passwdElm.addTextNode(password);
userNameTokenElm.addChildElement(userNameElm);
userNameTokenElm.addChildElement(passwdElm);
// add child elements to the root element
wsSecHeaderElm.addChildElement(userNameTokenElm);
// create SOAPHeader instance for SOAP envelope
SOAPHeader sh;
if(envelope.getHeader()==null){
logger.info("SOAPHeader null.Add header");
sh = envelope.addHeader();
}else{
logger.info("SOAPHeader already present");
sh = envelope.getHeader();
}
// add SOAP element for header to SOAP header object
sh.addChildElement(wsSecHeaderElm);
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
return true;
}
public java.util.Set<QName> getHeaders() {
return null;
}
}
The "myCRM.userName" & "myCRM.password" is defined in my application.properties file. And the classPath of application.properties is defined in applicationContext.xml:
<util:properties id="applicationProperties" location="classpath:/i18n/application.properties"/>
What is going wrong?
The Spring Context has to be made aware that it needs to load some autowired components on a specific class. The #Controller annotation and a reference in the spring-servlet.xml ensure just that.
You can try adding this to your spring-servlet.xml
<context:component-scan base-package="com.myPortlet.crmService" />
Also Add a #Controller annotation in your class to initiate auto wiring at server startup. Else your Properties instance will be null everytime you try to access it.
I had a similar problem trying injecting a dependency in my #webservice class. I solved it adding the method below in the class (org.springframework.web.context.support.SpringBeanAutowiringSupport;)
#PostConstruct
#WebMethod(exclude = true)
public void init() {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}

Resources