#8 financer-web-client has a hard dependency on push-service

Introduce proxy for push service so it is completely optional
This commit is contained in:
2021-03-03 21:20:04 +01:00
parent ae5e60806d
commit ddd1d3cc65
5 changed files with 199 additions and 11 deletions

View File

@@ -62,6 +62,7 @@
<dependency>
<groupId>de.77zzcx7.push-service</groupId>
<artifactId>push-service-client-lib</artifactId>
<optional>true</optional>
</dependency>
<!-- Financer dependencies -->

View File

@@ -7,13 +7,13 @@ import de.financer.dto.Order;
import de.financer.dto.SearchTransactionsResponseDto;
import de.financer.form.NewAccountForm;
import de.financer.model.*;
import de.financer.notification.Notification;
import de.financer.notification.PushServiceProxy;
import de.financer.notification.Urgency;
import de.financer.template.*;
import de.financer.template.exception.FinancerRestException;
import de.financer.util.ControllerUtils;
import de.financer.util.TransactionUtils;
import de.pushservice.client.dto.PayloadDto;
import de.pushservice.client.model.Urgency;
import de.pushservice.client.service.SubscriptionService;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -209,21 +209,22 @@ public class AccountController {
// ---------------------------------------------
@Autowired(required = false)
private SubscriptionService subscriptionService;
@Autowired
private PushServiceProxy pushServiceProxy;
private int counter = 0;
@GetMapping("/sendTestNotification")
public String sendTestNotification() {
if (this.subscriptionService != null && this.subscriptionService.isInitialized()) {
PayloadDto p = new PayloadDto("Hello from Financer!");
if (this.pushServiceProxy.isInitialized()) {
Notification notification = new Notification();
p.setTimestamp(System.currentTimeMillis());
p.setMessage("Test notification " + counter++);
p.setVibration(new int[] {100, 200, 300, 400, 500});
notification.setTitle("Hello from Financer!");
notification.setTopic("FINANCER-TEST");
notification.setMessage("Test notification " + counter++);
notification.setUrgency(Urgency.NORMAL);
try {
this.subscriptionService.notify(p, "FINANCER-TEST", Urgency.NORMAL);
this.pushServiceProxy.notify(notification);
}
catch(Exception e) {
e.printStackTrace();

View File

@@ -0,0 +1,40 @@
package de.financer.notification;
public class Notification {
private String title;
private String topic;
private String message;
private Urgency urgency;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Urgency getUrgency() {
return urgency;
}
public void setUrgency(Urgency urgency) {
this.urgency = urgency;
}
}

View File

@@ -0,0 +1,138 @@
package de.financer.notification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.stereotype.Component;
/**
* Class to really loosely couple the PushService to financer.
* The PushService dependency is optional in the POM, so if it is not available instantiation would fail,
* that's why the actual classes get proxied and coupling is done via reflection.
*/
@Component
public class PushServiceProxy implements InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(PushServiceProxy.class);
// #### Service constants
private static final String SERVICE_CLASS_NAME = "de.pushservice.client.service.SubscriptionService";
private static final String SERVICE_METHOD_IS_INITIALIZED = "isInitialized";
private static final String SERVICE_METHOD_NOTIFY = "notify";
// #### Payload constants
private static final String PAYLOAD_CLASS_NAME = "de.pushservice.client.dto.PayloadDto";
private static final String PAYLOAD_METHOD_SET_TIMESTAMP = "setTimestamp";
private static final String PAYLOAD_METHOD_SET_MESSAGE = "setMessage";
// #### Urgency constants
private static final String URGENCY_CLASS_NAME = "de.pushservice.client.model.Urgency";
private Class<?> subscriptionServiceClass;
private Class<?> payloadClass;
private Class<?> urgencyClass;
private Object subscriptionServiceInstance;
@Autowired
private AutowireCapableBeanFactory autowireCapableBeanFactory;
// #### Public methods mimicking SubscriptionService interface
public boolean isInitialized() {
return this.subscriptionServiceInstance != null && this.isInitializedInternal();
}
public void notify(Notification notification) {
if(!this.isInitialized()) {
LOGGER.warn("notify() called but not initialized!");
}
try {
this.subscriptionServiceClass
.getMethod(SERVICE_METHOD_NOTIFY, this.payloadClass, String.class, this.urgencyClass)
.invoke(this.subscriptionServiceInstance, toPayload(notification), notification
.getTopic(), toUrgency(notification));
} catch (ReflectiveOperationException e) {
LOGGER.error("Error while sending notification!", e);
}
}
// #### Initialization
@Override
public void afterPropertiesSet() throws Exception {
try {
this.subscriptionServiceClass = Class.forName(SERVICE_CLASS_NAME);
// Since we create the instance ourself no auto wiring by Spring
this.subscriptionServiceInstance = this.subscriptionServiceClass.getDeclaredConstructor().newInstance();
// Do the Spring wiring magic
this.autowireCapableBeanFactory.autowireBean(this.subscriptionServiceInstance);
this.payloadClass = Class.forName(PAYLOAD_CLASS_NAME);
this.urgencyClass = Class.forName(URGENCY_CLASS_NAME);
} catch (ReflectiveOperationException e) {
LOGGER.error("Error while sending notification!", e);
}
}
// #### Internal methods
private Object toUrgency(Notification notification) {
Object urgencyInstance = null;
try {
// Urgency is an Enum in PushClient as well
// Works by having the same name in both Urgency enums
urgencyInstance = this.urgencyClass.getDeclaredField(notification.getUrgency().name()).get(null);
} catch (ReflectiveOperationException e) {
LOGGER.error("Error while sending notification!", e);
}
return urgencyInstance;
}
private Object toPayload(Notification notification) {
Object payloadInstance = null;
try {
payloadInstance = this.payloadClass.getConstructor(String.class)
.newInstance(notification.getTitle());
this.payloadClass.getMethod(PAYLOAD_METHOD_SET_TIMESTAMP, long.class)
.invoke(payloadInstance, System.currentTimeMillis());
this.payloadClass.getMethod(PAYLOAD_METHOD_SET_MESSAGE, String.class)
.invoke(payloadInstance, notification.getMessage());
} catch (ReflectiveOperationException e) {
LOGGER.error("Error while sending notification!", e);
}
return payloadInstance;
}
private boolean isInitializedInternal() {
boolean retVal = false;
try {
final Object tmpRetVal = this.subscriptionServiceClass.getMethod(SERVICE_METHOD_IS_INITIALIZED)
.invoke(this.subscriptionServiceInstance);
if (Boolean.class.isAssignableFrom(tmpRetVal.getClass())) {
retVal = ((Boolean) tmpRetVal).booleanValue();
} else {
LOGGER.warn("Proxied isInitialized call returned non java.lang.Boolean value");
}
} catch (ReflectiveOperationException e) {
LOGGER.error("Error while sending notification!", e);
}
if(LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("isInitializedInternal() returns with %s", retVal));
}
return retVal;
}
}

View File

@@ -0,0 +1,8 @@
package de.financer.notification;
public enum Urgency {
VERY_LOW,
LOW,
NORMAL,
HIGH;
}