Display SpringBoot Actuator JSON string as HTML - spring-boot

How can I display the JSON string from SpringBoot Actuator as HTML?
So when I visit url localhost:8080/actuator/health
I see something like
Application status: UP
Database H2: UP
Database MySQL: UP
Diskspace: UP
And if there is some errors I display them too.
Thank you

You can overwrite the existing definition for EndpointWebExtension like below. You can write custom logic as per requirement.
#Component
#EndpointWebExtension(endpoint = HealthEndpoint.class)
public class CustomeEndpointWebExtension extends HealthEndpointWebExtension {
private static final String[] NO_PATH = {};
public HealthWebEndpointWebExtension(HealthContributorRegistry registry, HealthEndpointGroups groups) {
super(registry, groups);
}
//To write custom logic for /acuator/health
#ReadOperation
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, SecurityContext securityContext) {
System.out.println("##Start");
WebEndpointResponse<HealthComponent> health = health(apiVersion, securityContext, false, NO_PATH);
//Can write customer logic here as per requirment
System.out.println("##End"+health.getBody().getStatus());
return health;
}
//To write custom logic for /acuator/health/db
#ReadOperation
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, SecurityContext securityContext,
#Selector(match = Match.ALL_REMAINING) String... path) {
WebEndpointResponse<HealthComponent> health = health(apiVersion, securityContext, false, path);
System.out.println("#Status"+health.getBody().getStatus());
return health;
}
}
You can refer to below stack to convert json to html
How to convert a json object into HTML format in java?

Related

Togglz - Username Activation Strategy Implementation

I am trying to implement UsernameActivation Startegy in Springboot using togglz, but due to insufficient example/documentation on this, I am unable to do so. It is a simple Poc in maven. Here are my classes:
public enum Features implements Feature{
#Label("just a description")
#EnabledByDefault
HELLO_WORLD,
#Label("Hello World Feature")
#DefaultActivationStrategy(id = UsernameActivationStrategy.ID, parameters =
{#ActivationParameter(name = UsernameActivationStrategy.PARAM_USERS, value = "suga")
})
HELLO,
#Label("another descrition")
#EnabledByDefault
REVERSE_GREETING;
public boolean isActive() {
return FeatureContext.getFeatureManager().isActive(this);
}
}
#Component
public class Togglz implements TogglzConfig {
public Class<? extends Feature> getFeatureClass() {
return Features.class;
}
public StateRepository getStateRepository() {
return new FileBasedStateRepository(new File("/tmp/features.properties"));
}
public UserProvider getUserProvider() {
return new SpringSecurityUserProvider("ADMIN_ROLE");
}
}
I want to use UsernameActivation strategy but i am not sure what more code changes I need to do in order for it to work. I do know that it is somehow related to UserProvider. Also, I am not sure how will it compare the username value and how will it capture the current user value. Any idea around this will be of great help!
I had to override the getUserProvider method. Since I am using Spring for auto-configuration, and not extending ToggleConfig, I added this as a bean to load at startup.
#Bean
public UserProvider getUserProvider() {
return new UserProvider() {
#Override
public FeatureUser getCurrentUser() {
String username = <MyAppSecurityProvider>.getUserName();
boolean isAdmin = "admin".equals(username);
return new SimpleFeatureUser(username, isAdmin);
}
};
}
Note: I had to do this as my app uses our inbuilt security mechanism. Reading the documentation, it looks like its easier if you are using standard security such as Spring or Servlet.
My config in application.yml(the same thing you have in the annotation)
togglz:
features:
FRIST_FEATURE:
enabled: true
strategy: username
param:
users: user1,user2
SECOND_FEATURE:
enabled: true
strategy: username
param:
users: user2,user3

Spring HATEOAS RepresentationModelAssembler toCollectionModel()

I'm creating Spring Boot HATEOAS REST application. Code below shows how am I adding links, while GET request is send for specific Employee. I'm using RepresentationModelAssembler toModel function. There's also toCollectionModel function to Override, which I would like to use to convert List<Employees> to CollectionModel. -> This will be returned in /Employees/all endpoint.
And I dunno how to do that. So what I need is to pass List<Employees>, then all list elements needs to be processed by toModel functions, and then, like in toModel function I need possibility to add more links to it -> links to entire new collection (not individual items).
Looking forward for your answers!
#Component
public class EmployeeModelAssembler implements RepresentationModelAssembler<Employee, EntityModel<Employee>> {
#Override
public EntityModel<Employee> toModel(Employee employee) {
EntityModel<Employee> employeeEntityModel = EntityModel.of(employee);
Link selfLink = linkTo(methodOn(EmployeeController.class).getEmployeeById(employee.getId())).withSelfRel();
employeeEntityModel.add(selfLink);
return employeeEntityModel;
}
#Override
public CollectionModel<EntityModel<Employee>> toCollectionModel(Iterable<? extends Employee> entities) {
?? ?? ??
}
}
You can use something like this:
#GetMapping(produces = { "application/hal+json" })
public CollectionModel<Customer> getAllCustomers() {
List<Customer> allCustomers = customerService.allCustomers();
for (Customer customer : allCustomers) {
String customerId = customer.getCustomerId();
Link selfLink = linkTo(CustomerController.class).slash(customerId).withSelfRel();
customer.add(selfLink);
if (orderService.getAllOrdersForCustomer(customerId).size() > 0) {
Link ordersLink = linkTo(methodOn(CustomerController.class)
.getOrdersForCustomer(customerId)).withRel("allOrders");
customer.add(ordersLink);
}
}
Link link = linkTo(CustomerController.class).withSelfRel();
CollectionModel<Customer> result = CollectionModel.of(allCustomers, link);
return result;
}
Visit https://www.baeldung.com/spring-hateoas-tutorial#springhateoasinaction for detailed explanation

Internationalization file in SpringBoot Application

I have Springboot Application for Rest Service. And rest services are documented using Swagger/ springfox annotations, and I want use i18n file properties for internationalization feautures in my App....
as I do in the controller I dynamically retrieve the property of the 'message' in annotation #ApiResponses?
#ApiResponses(value = {
#ApiResponse(code = HttpURLConnection.HTTP_OK, message = "Successful login to platform"),
#ApiResponse(code = HttpURLConnection.HTTP_UNAUTHORIZED, message = "Unauthorized request to platform")
})
Thanks
Seems as of now (2018-08-08) it's currently not supported. You can take a look at the issue here: link
Had similar issue.
First if you look at current Springfox (Version 2.9.2) documentation, they supports following annotations out of the box:
#ApiParam#value()
#ApiImplicitParam#value()
#ApiModelProperty#value()
#ApiOperation#value()
#ApiOperation#notes()
#RequestParam#defaultValue()
#RequestHeader#defaultValue()
It took me some time to get dynamic #ApiResponse message, so I will post a reply to help others.
Solution is based on https://github.com/springfox/springfox/issues/1180
First I added following to my swagger configuration file:
#Bean
public TranslationOperationBuilderPlugin translationPlugin() {
return new TranslationOperationBuilderPlugin();
}
//important to keep this LOWEST_PRECEDENCE!!!
#Order(Ordered.LOWEST_PRECEDENCE)
public static class TranslationOperationBuilderPlugin implements OperationBuilderPlugin {
#Autowired
protected Environment env;
#Override
public boolean supports(DocumentationType delimiter) {
return true;
}
#Override
public void apply(OperationContext context) {
Set<ResponseMessage> messages = context.operationBuilder().build().getResponseMessages();
Set<ResponseMessage> translated = new HashSet<>();
for (ResponseMessage untranslated : messages) {
String translation = env.getProperty(untranslated.getMessage());
translated.add(new ResponseMessage(untranslated.getCode(),
translation,
untranslated.getResponseModel(),
untranslated.getHeaders(),
untranslated.getVendorExtensions()
));
}
context.operationBuilder().responseMessages(translated);
}
}
On controller class I add #PropertySource(value= "classpath:swagger.properties", encoding="UTF-8")
This file is located inside usual resources directory and contains code_400=my message
And on method in controller
#ApiResponses(value = {
#ApiResponse(code = 400, message = ResponseKeys.MESSAGE_400)
})
And finally ResponseKeys contains:
public class ResponseKeys {
/* 4xx messages */
public static final String MESSAGE_400 = "code_400";
}

Manually call Hibernate URL validator in spring-boot

I am using spring-boot 1.4.0 and hibernate-validator 5.2.0. I have a model which contains custom validator inside the custom validator i want to check whether the property value is a valid URL for that i need to call URLValidator in hibernate but no luck.Could anyone please guide me to resolve this issue
CustomValidator.java
#Component
public class BookValidator extends GenericValidator<Book, ConstraintValidatorContext> implements ConstraintValidator<ValidBooks, List<Book>> {
public BookValidator() {
addValidators();
}
private void addValidators() {
getValidators().add((book, context) -> {
boolean isValid = book.getUrl(); //here i want to check against Hibernate URL validator
if (!isValid) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("Book URL should be valid!")
.addConstraintViolation();
}
return isValid;
});
}
#Override
public void initialize(ValidBooks constraintAnnotation) {
}
}
How can i check whether the URL is a valid one
boolean isValid = book.getUrl(); using hibernate URLValidator?
This works:
AnnotationDescriptor<URL> descriptor = new AnnotationDescriptor<URL>( URL.class );
URL url = AnnotationFactory.create(descriptor);
URLValidator urlValidator = new URLValidator();
urlValidator.initialize(url);
boolean isValid = urlValidator.isValid(book.getUrl(), context);

Read Application Object from GemFire using Spring Data GemFire. Data stored using SpringXD's gemfire-json-server

I'm using the gemfire-json-server module in SpringXD to populate a GemFire grid with json representation of “Order” objects. I understand the gemfire-json-server module saves data in Pdx form in GemFire. I’d like to read the contents of the GemFire grid into an “Order” object in my application. I get a ClassCastException that reads:
java.lang.ClassCastException: com.gemstone.gemfire.pdx.internal.PdxInstanceImpl cannot be cast to org.apache.geode.demo.cc.model.Order
I’m using the Spring Data GemFire libraries to read contents of the cluster. The code snippet to read the contents of the Grid follows:
public interface OrderRepository extends GemfireRepository<Order, String>{
Order findByTransactionId(String transactionId);
}
How can I use Spring Data GemFire to convert data read from the GemFire cluster into an Order object?
Note: The data was initially stored in GemFire using SpringXD's gemfire-json-server-module
Still waiting to hear back from the GemFire PDX engineering team, specifically on Region.get(key), but, interestingly enough if you annotate your application domain object with...
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#type")
public class Order ... {
...
}
This works!
Under-the-hood I knew the GemFire JSONFormatter class (see here) used Jackson's API to un/marshal (de/serialize) JSON data to and from PDX.
However, the orderRepository.findOne(ID) and ordersRegion.get(key) still do not function as I would expect. See updated test class below for more details.
Will report back again when I have more information.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = GemFireConfiguration.class)
#SuppressWarnings("unused")
public class JsonToPdxToObjectDataAccessIntegrationTest {
protected static final AtomicLong ID_SEQUENCE = new AtomicLong(0l);
private Order amazon;
private Order bestBuy;
private Order target;
private Order walmart;
#Autowired
private OrderRepository orderRepository;
#Resource(name = "Orders")
private com.gemstone.gemfire.cache.Region<Long, Object> orders;
protected Order createOrder(String name) {
return createOrder(ID_SEQUENCE.incrementAndGet(), name);
}
protected Order createOrder(Long id, String name) {
return new Order(id, name);
}
protected <T> T fromPdx(Object pdxInstance, Class<T> toType) {
try {
if (pdxInstance == null) {
return null;
}
else if (toType.isInstance(pdxInstance)) {
return toType.cast(pdxInstance);
}
else if (pdxInstance instanceof PdxInstance) {
return new ObjectMapper().readValue(JSONFormatter.toJSON(((PdxInstance) pdxInstance)), toType);
}
else {
throw new IllegalArgumentException(String.format("Expected object of type PdxInstance; but was (%1$s)",
pdxInstance.getClass().getName()));
}
}
catch (IOException e) {
throw new RuntimeException(String.format("Failed to convert PDX to object of type (%1$s)", toType), e);
}
}
protected void log(Object value) {
System.out.printf("Object of Type (%1$s) has Value (%2$s)", ObjectUtils.nullSafeClassName(value), value);
}
protected Order put(Order order) {
Object existingOrder = orders.putIfAbsent(order.getTransactionId(), toPdx(order));
return (existingOrder != null ? fromPdx(existingOrder, Order.class) : order);
}
protected PdxInstance toPdx(Object obj) {
try {
return JSONFormatter.fromJSON(new ObjectMapper().writeValueAsString(obj));
}
catch (JsonProcessingException e) {
throw new RuntimeException(String.format("Failed to convert object (%1$s) to JSON", obj), e);
}
}
#Before
public void setup() {
amazon = put(createOrder("Amazon Order"));
bestBuy = put(createOrder("BestBuy Order"));
target = put(createOrder("Target Order"));
walmart = put(createOrder("Wal-Mart Order"));
}
#Test
public void regionGet() {
assertThat((Order) orders.get(amazon.getTransactionId()), is(equalTo(amazon)));
}
#Test
public void repositoryFindOneMethod() {
log(orderRepository.findOne(target.getTransactionId()));
assertThat(orderRepository.findOne(target.getTransactionId()), is(equalTo(target)));
}
#Test
public void repositoryQueryMethod() {
assertThat(orderRepository.findByTransactionId(amazon.getTransactionId()), is(equalTo(amazon)));
assertThat(orderRepository.findByTransactionId(bestBuy.getTransactionId()), is(equalTo(bestBuy)));
assertThat(orderRepository.findByTransactionId(target.getTransactionId()), is(equalTo(target)));
assertThat(orderRepository.findByTransactionId(walmart.getTransactionId()), is(equalTo(walmart)));
}
#Region("Orders")
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#type")
public static class Order implements PdxSerializable {
protected static final OrderPdxSerializer pdxSerializer = new OrderPdxSerializer();
#Id
private Long transactionId;
private String name;
public Order() {
}
public Order(Long transactionId) {
this.transactionId = transactionId;
}
public Order(Long transactionId, String name) {
this.transactionId = transactionId;
this.name = name;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public Long getTransactionId() {
return transactionId;
}
public void setTransactionId(final Long transactionId) {
this.transactionId = transactionId;
}
#Override
public void fromData(PdxReader reader) {
Order order = (Order) pdxSerializer.fromData(Order.class, reader);
if (order != null) {
this.transactionId = order.getTransactionId();
this.name = order.getName();
}
}
#Override
public void toData(PdxWriter writer) {
pdxSerializer.toData(this, writer);
}
#Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Order)) {
return false;
}
Order that = (Order) obj;
return ObjectUtils.nullSafeEquals(this.getTransactionId(), that.getTransactionId());
}
#Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getTransactionId());
return hashValue;
}
#Override
public String toString() {
return String.format("{ #type = %1$s, id = %2$d, name = %3$s }",
getClass().getName(), getTransactionId(), getName());
}
}
public static class OrderPdxSerializer implements PdxSerializer {
#Override
public Object fromData(Class<?> type, PdxReader in) {
if (Order.class.equals(type)) {
return new Order(in.readLong("transactionId"), in.readString("name"));
}
return null;
}
#Override
public boolean toData(Object obj, PdxWriter out) {
if (obj instanceof Order) {
Order order = (Order) obj;
out.writeLong("transactionId", order.getTransactionId());
out.writeString("name", order.getName());
return true;
}
return false;
}
}
public interface OrderRepository extends GemfireRepository<Order, Long> {
Order findByTransactionId(Long transactionId);
}
#Configuration
protected static class GemFireConfiguration {
#Bean
public Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", JsonToPdxToObjectDataAccessIntegrationTest.class.getSimpleName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", "warning");
return gemfireProperties;
}
#Bean
public CacheFactoryBean gemfireCache(Properties gemfireProperties) {
CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();
cacheFactoryBean.setProperties(gemfireProperties);
//cacheFactoryBean.setPdxSerializer(new MappingPdxSerializer());
cacheFactoryBean.setPdxSerializer(new OrderPdxSerializer());
cacheFactoryBean.setPdxReadSerialized(false);
return cacheFactoryBean;
}
#Bean(name = "Orders")
public PartitionedRegionFactoryBean ordersRegion(Cache gemfireCache) {
PartitionedRegionFactoryBean regionFactoryBean = new PartitionedRegionFactoryBean();
regionFactoryBean.setCache(gemfireCache);
regionFactoryBean.setName("Orders");
regionFactoryBean.setPersistent(false);
return regionFactoryBean;
}
#Bean
public GemfireRepositoryFactoryBean orderRepository() {
GemfireRepositoryFactoryBean<OrderRepository, Order, Long> repositoryFactoryBean =
new GemfireRepositoryFactoryBean<>();
repositoryFactoryBean.setRepositoryInterface(OrderRepository.class);
return repositoryFactoryBean;
}
}
}
So, as you are aware, GemFire (and by extension, Apache Geode) stores JSON in PDX format (as a PdxInstance). This is so GemFire can interoperate with many different language-based clients (native C++/C#, web-oriented (JavaScript, Pyhton, Ruby, etc) using the Developer REST API, in addition to Java) and also to be able to use OQL to query the JSON data.
After a bit of experimentation, I am surprised GemFire is not behaving as I would expect. I created an example, self-contained test class (i.e. no Spring XD, of course) that simulates your use case... essentially storing JSON data in GemFire as PDX and then attempting to read the data back out as the Order application domain object type using the Repository abstraction, logical enough.
Given the use of the Repository abstraction and implementation from Spring Data GemFire, the infrastructure will attempt to access the application domain object based on the Repository generic type parameter (in this case "Order" from the "OrderRepository" definition).
However, the data is stored in PDX, so now what?
No matter, Spring Data GemFire provides the MappingPdxSerializer class to convert PDX instances back to application domain objects using the same "mapping meta-data" that the Repository infrastructure uses. Cool, so I plug that in...
#Bean
public CacheFactoryBean gemfireCache(Properties gemfireProperties) {
CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();
cacheFactoryBean.setProperties(gemfireProperties);
cacheFactoryBean.setPdxSerializer(new MappingPdxSerializer());
cacheFactoryBean.setPdxReadSerialized(false);
return cacheFactoryBean;
}
You will also notice, I set the PDX 'read-serialized' property (cacheFactoryBean.setPdxReadSerialized(false);) to false in order to ensure data access operations return the domain object and not the PDX instance.
However, this had no affect on the query method. In fact, it had no affect on the following operations either...
orderRepository.findOne(amazonOrder.getTransactionId());
ordersRegion.get(amazonOrder.getTransactionId());
Both calls returned a PdxInstance. Note, the implementation of OrderRepository.findOne(..) is based on SimpleGemfireRepository.findOne(key), which uses GemfireTemplate.get(key), which just performs Region.get(key), and so is effectively the same as (ordersRegion.get(amazonOrder.getTransactionId();). The outcome should not be, especially with Region.get() and read-serialized set to false.
With the OQL query (SELECT * FROM /Orders WHERE transactionId = $1) generated from the findByTransactionId(String id), the Repository infrastructure has a bit less control over what the GemFire query engine will return based on what the caller (OrderRepository) expects (based on the generic type parameter), so running OQL statements could potentially behave differently than direct Region access using get.
Next, I went onto try modifying the Order type to implement PdxSerializable, to handle the conversion during data access operations (direct Region access with get, OQL, or otherwise). This had no affect.
So, I tried to implement a custom PdxSerializer for Order objects. This had no affect either.
The only thing I can conclude at this point is something is getting lost in translation between Order -> JSON -> PDX and then from PDX -> Order. Seemingly, GemFire needs additional type meta-data required by PDX (something like #JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#type") in the JSON data that PDXFormatter recognizes, though I am not certain it does.
Note, in my test class, I used Jackson's ObjectMapper to serialize the Order to JSON and then GemFire's JSONFormatter to serialize the JSON to PDX, which I suspect Spring XD is doing similarly under-the-hood. In fact, Spring XD uses Spring Data GemFire and is most likely using the JSON Region Auto Proxy support. That is exactly what SDG's JSONRegionAdvice object does (see here).
Anyway, I have an inquiry out to the rest of the GemFire engineering team. There are also things that could be done in Spring Data GemFire to ensure the PDX data is converted, such as making use of the MappingPdxSerializer directly to convert the data automatically on behalf of the caller if the data is indeed of type PdxInstance. Similar to how JSON Region Auto Proxying works, you could write AOP interceptor for the Orders Region to automagicaly convert PDX to an Order.
Though, I don't think any of this should be necessary as GemFire should be doing the right thing in this case. Sorry I don't have a better answer right now. Let's see what I find out.
Cheers and stay tuned!
See subsequent post for test code.

Resources