Spring: Can't map YAML to list of objects - spring

I have a yaml file with errors and error messages. I'm attempting to load them into a class, RefreshErrors within a common jar used by my Spring Boot microservices. The common jar does have Spring Boot on the classpath, but there is no SpringBootApplication - it's simply included by other applications. So that may be why I'm not getting any values in RefreshErrors?
Here's the yaml, which is in application.yml, located under src/main/resources:
refreshErrorsProperties:
refreshErrors:
-
recordStatus: "Error"
errorCode: "502 Bad Gateway"
errorMessage: "AWS service is tempoarily not available. ESS has been notified. Please try refreshing again after the service becomes available."
-
recordStatus: "Error"
errorCode: "503 Service Temporarily Unavailable"
errorMessage: "AWS service is tempoarily not available. ESS has been notified. Please try refreshing again after the service becomes available."
-
...
And here's my configuration for the class I want the yaml mapped to:
#Data
#Component
#ConfigurationProperties(prefix="refreshErrors")
public class RefreshErrorsProperties {
private List<RefreshErrors> refreshErrors = new ArrayList<>();
}
The RefreshErrors class:
#Data
#AllArgsConstructor
public class RefreshErrors {
private String errorCode;
private String recordStatus;
private String errorMessage;
}
I autowire RefreshErrors in another class (see below) and I do get an object reference, but all the values inside (like errorCode etc) are empty. Any help would be appreciated!
#Autowired
public void setRefreshErrorsProperties(RefreshErrorsProperties refreshErrorsProperties) {
RefreshJobDetailHelper.refreshErrorsProperties = refreshErrorsProperties;
}
...
RefreshErrors error;
if (exception != null) {
String fullException = ExceptionUtils.getStackTrace(exception);
error = refreshErrorsProperties.getRefreshErrors().stream()
.filter(f -> fullException.contains(f.getErrorCode()))
.findAny()
.orElseGet((Supplier<? extends RefreshErrors>) new RefreshErrors("Error", ERROR.getValue(), "An error has occurred while refreshing this record. ESS has been notified. Please try refreshing again after the issue has been resolved."));
} else {
error = new RefreshErrors("No data", ERROR_NO_DATA.getValue(), "Data is not available in the source for this product, domain, and effective date.");
}

The issue here is with the prefix #ConfigurationProperties(prefix = "refreshErrors"). It should be like #ConfigurationProperties, with no prefix, the prefix is required when you need to map properties under the prefix, but refreshErrors is the property you want to map and not the prefix. The below code works fine.
I have refactored a bit :
properties file:
refresh-errors:
-
record-status: "Error"
error-code: "502 Bad Gateway"
error-message: "AWS service is tempoarily not available. ESS has been notified. Please try refreshing again after the service becomes available."
-
record-status: "Error"
error-code: "503 Service Temporarily Unavailable"
error-message: "AWS service is tempoarily not available. ESS has been notified. Please try refreshing again after the service becomes available."
PropertiesMapperClass :
#Data
#Component
#ConfigurationProperties
public class RefreshErrorsProperties {
private List<RefreshErrors> refreshErrors = new ArrayList<>();
#Data
public static class RefreshErrors {
private String errorCode;
private String recordStatus;
private String errorMessage;
}
}

if you don't change your annotation,the yaml file node refreshErrors's attribute should not be a list,
the yaml file should be
refresh-errors:
record-status: "Error"
error-code: "502 Bad Gateway"
error-message: "AWS service ..."
which may not be what you want,
if you don't change your yaml file,
the code should be like Mohit Singh shows

Related

Spring with Quartz worker initialize a member only once?

I have a QuartzJob with multiple methods that call the same rest API
The class holds a member (configuration ) with the rest api configuration.
The rest api configuration is initialize once using getConfiguration method.
public QuartzJob implements Job{
#Value("${API_URL}")private String apiUrl;
ApiCallConfiguration configuration = getConfiguration();
method1(){call api using configuration }
method2(){call api using configuration }
method3(){call api using configuration }
}
When I try to create a constructor, it fails probably because the API values are not constructed yet... #Value("${API_URL}")private String apiUrl;
Exception below.
what is the correct way to initialize the configuration property only once in the QuartzJob after everything is set up?
Caused by: java.lang.NullPointerException: Configuration key Caused by: java.lang.NullPointerException: Configuration key API_URL has no value
at org.apache.commons.lang3.Validate.notBlank(Validate.java:451)
at com.xerox.printerTech.quartz.jobs.PrinterVerificationJob.getPrinterRestApiConfig(PrinterVerificationJob.java:722)
at com.xerox.printerTech.quartz.jobs.PrinterVerificationJob.<init>(PrinterVerificationJob.java:164)
... 10 more has no value
at org.apache.commons.lang3.Validate.notBlank(Validate.java:451)
at com.xerox.printerTech.quartz.jobs.PrinterVerificationJob.getPrinterRestApiConfig(PrinterVerificationJob.java:722)
at com.xerox.printerTech.quartz.jobs.PrinterVerificationJob.<init>(PrinterVerificationJob.java:164)
... 10 more
I was able to resolve this issue using #Component and #Autowired
here is a sample code:
public QuartzJob implements Job{
#Autowired
ApiCallConfiguration configuration;
method1(){configuration.getApiUrl(); }
method2(){configuration.apiUseSSL(); }
method3(){configuration.getApiUserName(); }
}
#Component
public class ApiCallConfiguration {
public ApiCallConfiguration(
#Value("${API_URL}") String apiUrl,
#Value("${API_URL_SSL}") String apiUseSSL
#Value("${API_USER_NAME}") String apiUserName
){
this.apiUrl = apiUrl;
this.apiUseSSL = apiUseSSL;
this.apiUserName = apiUserName;
}
}

How to edit Whitelabel Error Page in Spring

I want only to edit Spring White Label Page. I see a lot of tutorials for deleting this page, but I want only to change some text here, for example Error 404 - Go back! Any tutorial?
Thanks!
If you are using Spring Boot version 1.4+, custom error pages may be named according to their appropriate error code (e.g. 404.html) and placed in the /src/main/resources/public/error directory for static files or the /src/main/resources/templates/error directory if using a template engine. See Spring Boot and custom 404 error page for more details. You may alternatively follow the steps below to implement custom error pages.
Set the server.error.whitelabel.enabled property to false in your application.properties file. This will disable the error page and show an error page originating from the underlying application container.
server.error.whitelabel.enabled=false
Create custom error pages and save them in resources/templates directory. These pages may be created and named for different HTTP status codes, e.g.: error-404, error-500, etc.
Create a new Controller class that implements the ErrorController interface and override the getErrorPath method. Create a mapping for the path returned by the getErrorPath method. The method that handles this mapping can read the error code and return the appropriate custom error page.
#Controller
public class MyErrorController implements ErrorController {
private static final String ERROR_PATH = "/error";
#RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status =
request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());
if(statusCode == HttpStatus.NOT_FOUND.value()) {
return "error-404";
}
else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()){
return "error-500";
}
}
return "error";
}
#Override
public String getErrorPath() {
return ERROR_PATH ;
}
}

Properties of custom Spring Cloud Service Connector not published to cloud.services.*

I created a custom Spring Cloud Service Connector by defining the following two classes:
#ServiceLabel("kafka")
public class KafkaServiceInfo extends BaseServiceInfo {
private static Logger logger = Logger.getLogger(KafkaServiceInfo.class.getName());
public static final String BROKERS = "brokers";
public static final String REGISTRY = "schemaregistry";
protected List<String> brokers;
protected String registry;
public KafkaServiceInfo(String id, List<String> brokers, String registry) {
super(id);
this.brokers = brokers;
this.registry = registry;
}
#ServiceProperty
public String getRegistry() {
return registry;
}
#ServiceProperty
public List<String> getBrokers() {
return brokers;
}
}
And this class:
public class KafkaServiceInfoCreator extends CloudFoundryServiceInfoCreator<KafkaServiceInfo> {
private static Logger logger = Logger.getLogger(KafkaServiceInfoCreator.class.getName());
public static final String USER_PROVIDED_SERVICE_NAME = "kafka";
public KafkaServiceInfoCreator() {
super(new Tags(USER_PROVIDED_SERVICE_NAME), null);
}
public KafkaServiceInfo createServiceInfo(Map<String, Object> serviceData) {
String id = getId(serviceData);
Map<String, Object> credentials = getCredentials(serviceData);
List<String> brokers = (List<String>) credentials.get(KafkaServiceInfo.BROKERS);
String registry = (String) credentials.get(KafkaServiceInfo.REGISTRY);
logger.info("KafkaServiceInfo created for Cloud Foundry Service \"" + id + "\"");
logger.info("Kafka Brokers configured for Cloud Foundry Service: " + brokers.toString());
logger.info("Schema Registry configured for Cloud Foundry Service: " + registry);
return new KafkaServiceInfo(id, brokers, registry);
}
#Override
public boolean accept(Map<String, Object> serviceData) {
return getId(serviceData).contains(USER_PROVIDED_SERVICE_NAME);
}
}
On my PCF instance, I created a user-provided service that looks in the VCAPS env variables like this:
"user-provided": [
{
"credentials": {
"brokers": [
"<some-ip-here>:29092"
],
"schemaregistry": "http://<some-ip-here>:8081"
},
"label": "user-provided",
"name": "kafka",
"syslog_drain_url": "",
"tags": [],
"volume_mounts": []
}
]
I also added the service definition file into the META-INF folder.
src/main/resources/META-INF/services/org.springframework.cloud.cloudfoundry.CloudFoundryServiceInfoCreator
with the content:
path.to.my.file.KafkaServiceInfoCreator
I would now expect to see the properties whose getters are annotated with #ServiceProperty in the cloud.services.kafka.* path. However, they do not show up. Instead, I just have the following 2 entries:
"cloud.services.kafka.id": "kafka",
"cloud.services.null.id": "kafka",
I am wondering what is going wrong here and also why I have the second entry with the "null" in between.
Any ideas what could be wrong here? The classes somehow seem to be found since I get the log messages defined in the creator class above.
Regards, Lars
The #ServiceProperty annotation expects to be provided with either a category or name attribute. These values are used to build the key that is placed into the cloud.services map. If neither category or name attribute are provided, the #ServiceProperty annotation does not result in the property appearing in the map.
A typical usage is #ServiceProperty(category="connection"), with the name defaulting to the name of the property using bean naming rules. In your case, adding the category="connection" attribute should result in
"cloud.services.kafka.id": "kafka",
"cloud.services.kafka.connection.registry": "http://<some-ip-here>:8081",
"cloud.services.kafka.connection.brokers": ["<some-ip-here>:29092"],
I'm not sure yet where the "cloud.services.null.id": "kafka" entry is coming from. Let me know if that still appears after adding the attributes to the #ServiceProperty annotation.
After digging quite a bit deeper, I found the "reason" explained here.
The cloud foundry java buildpack includes the auto-reconfiguration library which by itself contains a copy of the org.springframework.cloud namespace. This copy does NOT consider any custom ServiceInfo classes.
An since it is also this auto-reconfiguration that exposes the cloud.services.* properties to the environment, my personal ones are not picked-up and exposed as well. So I will switch this auto-reconfiguration off and configure the required stuff manually.
The spring cloud connector documentation is misleading here as well, since the properties in cloud.service.* are only added by java auto reconfiguration to the environment.

Custom JASPIC on WebSphere error message

Though similar, the specific problem I have is not addressed in Use JASPIC auth module on WebSphere 8.5
I am getting the following error message:
SECJ8027E: The path and name of file where JASPI persistent registrations are stored must be specified using property com.ibm.websphere.jaspi.configuration.
I can set the custom property in the administration to some existing folder but I wanted to make sure that is the right approach or if there is some step I was missing.
Note I am specifically using the "embedded in application" approach rather than a server installed JASPIC module so I have something like this
#WebListener
public class JaspicInitializer implements
ServletContextListener {
#Override
public void contextInitialized(final ServletContextEvent sce) {
final Map<String, String> options = new HashMap<>();
AuthConfigFactory.getFactory()
.registerConfigProvider(AuthModuleConfigProvider.class.getName(), options, "HttpServlet", null, null);
}
}
I had the error on both WebSphere 8.5.5.11 and 9.0.0.3
From #Uux comment, I changed the way I do the registration so it no longer give the error.
#WebListener
public class JaspicInitializer implements
ServletContextListener {
private String registrationID;
#Override
public void contextDestroyed(final ServletContextEvent sce) {
AuthConfigFactory.getFactory().removeRegistration(registrationID);
}
#Override
public void contextInitialized(final ServletContextEvent sce) {
final ServletContext context = sce.getServletContext();
registrationID = AuthConfigFactory.getFactory()
.registerConfigProvider(new AuthModuleConfigProvider(), "HttpServlet",
context.getVirtualServerName() + " " + context.getContextPath(), "JEE Sample");
}
}
Also WebSphere Global Security needs to be configured with
Enable application security
Enable Java Authentication SPI (JASPI)

Map UnsupportedMediaTypeException using ExceptionMapper

Is there a place where it is clearly documented that I cannot map UnsupportedMediaTypeException (because it's a rest easy exception and not custom application exception) using the javax.ws.rs.ext.ExceptionMapper?
I want to prove that to my client. Or another thing I would like to do is map this exception to a Response that can be fetched at the client to show the error. Right now when this exception is thrown it provides no information to the client as the application ends abruptly.
Any help would be appreciated.
Thanks
You can map this exception. Why not? Do you get an error?
This code should do the job
#Provider
public class EJBExceptionMapper implements ExceptionMapper<org.jboss.resteasy.spi.UnsupportedMediaTypeException>{
Response toResponse(org.jboss.resteasy.spi.UnsupportedMediaTypeException exception) {
return Response.status(415).build();
}
}
Don't forget to declare that provider in Spring configuration file.
If you want to provide more information to the client create class
#XmlRootElement
public class Error{
private String message;
//getter and setter for message field
}
and then you can
#Provider
public class EJBExceptionMapper implements ExceptionMapper<org.jboss.resteasy.spi.UnsupportedMediaTypeException>{
Response toResponse(org.jboss.resteasy.spi.UnsupportedMediaTypeException exception) {
Error error = new Error();
error.setMessage("Whatever message you want to send to user");
return Response.entity(error).status(415).build();
}
}
If you don't want to use Error entity simply pass a string to Response.entity() call.
If you want to catch whatever is thrown in you application create generic exception mapper:
#Provider
public class ThrowableMapper implements ExceptionMapper<Throwable> {
public Response toResponse(Throwable t) {
ErrorDTO errorDTO = new ErrorDTO(code);
return Response.status(500).build();
}
}

Resources