I have a Spring Boot RESTful API to receive and send SMS to clients. My application connects to our local SMS server and receives and pushes SMS to clients via mobile operators. My application works well. But I wanted to optimize my application by implementing cache. I am using the Simple cache of Spring Boot. I face some challenges when creating new SMS.
All SMSes sent/received are in the form of conversations (per ticket) and have clients attached to it. So I faced difficult saving a client into the cache. Below is createClient() snippet:
#Transactional
#Caching(evict = {
#CacheEvict("allClientsPage"),
#CacheEvict("countClients")
}, put = {
#CachePut(value = "clients", key = "#result.id", unless="#result != null"),
#CachePut(value = "clientsByPhone", key = "#result.phoneNumber", unless="#result != null")
})
public Client create(Client client) {
Client c = new Client();
if (client.getName() != null) c.setName(client.getName().trim());
c.setPhoneNumber(client.getPhoneNumber().trim());
/**---***/
c.setCreatedAt(new Date());
return clientRepository.save(c);
}
When I tried creating a new client, a
org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'id' cannot be found on null
is thrown.
Any assistance shall be greatly appreciated.
instead of using unless="# result! = null" use condition="#result != null"
Related
I'm trying to modify existing Java app (WildFly, Jboss, oracle) which currently working fine as using persistence-unit and EntityManager connect to Oracle database(using standalone.xml and persistence.xml). However, I need to create every time new connection to database for the user which calls new GET API Endpoint using credentials from the HttpHeaders. Currently, I'm creating new entitymanager object which session is commit, rollback nad close. Unfortunately time response for every call become higher and higher. There is warning about "PersistenceUnitUser" being already registered and memory usage constantly growing. So that is bad solution.
Is there any proper way to do it, which works witout any harms ?
P.S.
Currently app using standalone.xml and persistence.xml. And that is working fine. I'm calling java api endpoint using entity manager being connected as Admin user/pass but I need to create new connection using user/pass from the httpHeaders and call one sql statement to see proper results as ORACLE uses reserved word such us: 'user'. For instance : select * from table where create_usr = user. When done 'Main EntityManager will use data from it to continue some process.
Please see code example below :
#GET
#Path("/todo-list-enriched")
#Produces(MediaType.APPLICATION_JSON)
public Response getToDoListEnriched(#Context HttpHeaders httpHeaders, #QueryParam("skip") int elementNumber, #QueryParam("take") int pageSize, #QueryParam("orderby") String orderBy)
{
String userName = httpHeaders.getHeaderString(X_USER_NAME);
String userName = httpHeaders.getHeaderString(X_PASSWORD);
EntityManager entityManager = null;
try {
Map<String, String> persistenceMap = new HashMap<String, String>();
persistenceMap.put("hibernate.dialect","org.hibernate.dialect.Oracle8iDialect");
persistenceMap.put("hibernate.connection.username", asUserName);
persistenceMap.put("hibernate.connection.password", asPassword);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("PersistenceUnitUser", persistenceMap);
entityManager = emf.createEntityManager();
if (!entityManager.getTransaction().isActive()) {
entityManager.getTransaction().begin();
}
-- Do some works as select, update, select
-- and after that
if (entityManager.getTransaction().isActive()) {
entityManager.getTransaction().commit();
}
}
catch (Exception ex)
{
if (entityManager != null && entityManager.getTransaction().isActive()) {
entityManager.getTransaction().rollback();
}
}
finally {
if (entityManager != null && entityManager.isOpen()) {
entityManager.close();
}
}
}
}
``
Best Regards
Marcin
You should define a connection pool and a datasource in the standalone.xml (cf. https://docs.wildfly.org/26.1/Admin_Guide.html#DataSource) and then use it in your persistence.xml and inject the EntitytManager in your rest service class (cf. https://docs.wildfly.org/26.1/Developer_Guide.html#entity-manager).
You may look at this example application: https://github.com/wildfly/quickstart/tree/main/todo-backend
What we have on start
Let's say there is a simple Spring Boot application which provides an API for some frontend. Tech stack is quite regular: Kotlin, Gradle, Spring WebMVC, PostgreSQL, Keycloak.
The frontend interacts with the app synchronously via HTTP. Client authenticates with JWT token.
The business task
There is a list of events that could be raised somewhere in the system. User should be notified about them.
User is able to subscribe to one or more event notification. Subscriptions is just a pair of user_id + event_type_id persisted in dedicated Postgres table.
When event X is being raised we should find all the users subscribed to it and send them some data via Websocket
Configuration
Let's configure Spring first. Spring uses a STOMP over Websocket protocol.
Add dependencies to build.gradle.kts
implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("org.springframework:spring-messaging")
Add a Websocket config
#Configuration
#EnableWebSocket
#EnableWebSocketMessageBroker
class WebsocketConfig(
private val websocketAuthInterceptor: WebsocketAuthInterceptor
) : WebSocketMessageBrokerConfigurer {
override fun configureMessageBroker(config: MessageBrokerRegistry) {
config.enableSimpleBroker("/queue/")
config.setUserDestinationPrefix("/user")
}
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
registry.addEndpoint("/ws").setAllowedOrigins("*")
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS()
}
override fun configureClientInboundChannel(registration: ChannelRegistration) {
registration.interceptors(websocketAuthInterceptor) // we'll talk later about this
}
}
registerStompEndpoints is about how to establish connection with our websocket. The config allows frontend interact with us via SockJS of Websocket libraries. What they are and what the difference is not a today's topic
configureMessageBroker is about how we will interact with the frontend after the connection established.
configureClientInboundChannel is about message interception. Let talk about it later.
Add /ws/** path to ignored patterns in the Spring Security config.
Connection establishing
On the frontend side it should looks something like that (JavaScript)
const socket = new WebSocket('ws://<api.host>/ws')
//const socket = new SockJS('http://<api.host>/ws') // alternative way
const headers = {Authorization: 'JWT token here'};
const stompClient = Stomp.over(socket, {headers});
stompClient.connect(
headers,
function (frame) {
stompClient.subscribe('/user/queue/notification',
function (message) { ...message processing... });
});
The key moment is to pass an authorization header. It is not a HTTP header. And initial HTTP handshake itself will not be authorized. This is the reason of adding /ws/** to ignored patterns.
We need the header and the token because we want to allow websocket connection only for authorized users and also we want to know which exactly user is connected.
Authentication
Now we are adding the authentication mechanism
#Component
class WebsocketAuthInterceptor(
private val authService: AuthService, //your implementation
private val sessionStore: WebsocketUserSessionStore
) : ChannelInterceptor {
override fun preSend(message: Message<*>, channel: MessageChannel): Message<*>? {
val accessor: StompHeaderAccessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor::class.java)
val sessionId: String = accessor.messageHeaders["simpSessionId"].toString()
if (StompCommand.CONNECT == accessor.command) {
val jwtToken: String = accessor.getFirstNativeHeader("Authorization")
val token: AccessToken = authService.verifyToken(jwtToken)
val userId: Long = token.otherClaims["user_id"].toString().toLong()
sessionStore.add(sessionId, userId)
} else if (StompCommand.DISCONNECT == accessor.command) {
sessionStore.remove(sessionId)
}
return message
}
}
The point is to link a random generating websocket session ID with a user ID from our Spring Security store and persist that pair during the session life. JWT token should be parsed from the message headers.
Then by the given token a user ID should be obtained. Implementation of that part depends on what Spring Security config you exactly have. In case of Keycloack there is a useful static method org.keycloak.adapters.rotation.AdapterTokenVerifier::verifyToken
WebsocketUserSessionStore is just a map for linking session_id with user_id It may looks like the following code. Remember the concurrent access of course
#Component
class WebsocketUserSessionStore {
private val lock = ReentrantLock()
private val store = HashMap<String, Long>()
fun add(sessionId: String, userId: Long) = lock.withLock {
store.compute(sessionId) { _, _ -> userId }
}
fun remove(sessionId: String) = lock.withLock {
store.remove(sessionId)
}
fun remove(userId: Long) = lock.withLock {
store.values.remove(userId)
}
}
Notification actually
So event A was raised somewhere inside business logic. Let's implement a websocket publisher.
#Component
class WebsocketPublisher(
private val messagingTemplate: SimpMessagingTemplate,
private val objectMapper: ObjectMapper,
private val sessionStore: WebsocketUserSessionStore,
private val userNotificationRepository: UserNotificationRepository
) {
suspend fun publish(eventType: EventType, eventData: Any) {
val userIds = userNotificationRepository.getUserSubscribedTo(eventType)
val sessionIds = sessionStore.getSessionIds(userIds)
sessionIds.forEach {
messagingTemplate.convertAndSendToUser(
it,
"/queue/notification",
objectMapper.writeValueAsString(eventData),
it.toMessageHeaders()
)
}
}
private fun String.toMessageHeaders(): MessageHeaders {
val headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE)
headerAccessor.sessionId = this
headerAccessor.setLeaveMutable(true)
return headerAccessor.messageHeaders
}
}
EventType is an enumeration of event types the system has.
UserNotificationRepository is just a part of data persistence layer (Hibernate|JOOQ repository, MyBatis mapper or smth). Function getUserSubscribedTo should do something like select user_id from user_subscription where event_type_id = X.
The rest of code is plenty straightforward. By the giving userIds it is possible to obtain living websocket sessions. Then for every session convertAndSendToUser function should be called.
I think they have the tutorial to build the WebSocket push-notification service.
I have a Spring Boot Java REST application with many APIs exposed to our clients and UI. I was tasked with implementing a Transaction logging framework that will capture the incoming transactions along with the response we send.
I have this working with Spring AOP and an Around inspect and I'm currently utilizing the HttpServletRequest and HttpServletResponse objects to obtain a lot of the data I need.
From my local system I am not having any issues capturing the server used since I'm connecting to my system directly. However, once I deployed my code I saw that the load balancer URL was being captured instead of the actual server name.
I am also using Eureka to discover the API by name as it's only a single application running on HAProxy.
Imagine this flow:
/*
UI -> https://my-lb-url/service-sidecar/createUser
HAProxy directs traffic to -> my-lb-url/service-sidecar/ to one of below:
my-server-1:12345
my-server-2:12345
my-server-3:12345
Goal : http://my-server-1:1235/createUser
Actual: https://my-lb-url/createUser
Here is the code I am using to get the incoming URL.
String url = httpRequest.getRequestURL().toString();
if(httpRequest.getQueryString() != null){
transaction.setApi(url + "?" + httpRequest.getQueryString());
} else {
transaction.setApi(url);
}
Note:
I am not as familiar with HAProxy/Eurkea/etc. as I would like to be. If something stated above seems off or wrong then I apologize. Our system admin configured those and locked the developers out.
UPDATE
This is the new code I am using to construct the Request URL, but I am still seeing the output the same.
// Utility Class
public static String constructRequestURL(HttpServletRequest httpRequest) {
StringBuilder url = new StringBuilder(httpRequest.getScheme());
url.append("://").append(httpRequest.getServerName());
int port = httpRequest.getServerPort();
if(port != 80 && port != 443) {
url.append(":").append(port);
}
url.append(httpRequest.getContextPath()).append(httpRequest.getServletPath());
if(httpRequest.getPathInfo() != null) {
url.append(httpRequest.getPathInfo());
}
if(httpRequest.getQueryString() != null) {
url.append("?").append(httpRequest.getQueryString());
}
return url.toString();
}
// Service Class
transaction.setApi(CommonUtil.constructRequestURL(httpRequest));
I found a solution to this issue, but it's not the cleanest route and I would gladly take another suggestion if possible.
I am autowiring the port number from my application.yml.
I am running the "hostname" command on the Linux server that is hosting the application to determine the server fulfilling the request.
Now the URL stored in the Transaction Logs is accurate.
--
#Autowired
private int serverPort;
/*
* ...
*/
private String constructRequestURL(HttpServletRequest httpRequest) {
StringBuilder url = new StringBuilder(httpRequest.getScheme())
.append("://").append(findHostnameFromServer()).append(":").append(serverPort)
.append(httpRequest.getContextPath()).append(httpRequest.getServletPath());
if(httpRequest.getPathInfo() != null) {
url.append(httpRequest.getPathInfo());
}
if(httpRequest.getQueryString() != null) {
url.append("?").append(httpRequest.getQueryString());
}
return url.toString();
}
private String findHostnameFromServer(){
String hostname = null;
LOGGER.info("Attempting to Find Hostname from Server...");
try {
Process process = Runtime.getRuntime().exec(new String[]{"hostname"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
hostname = reader.readLine();
}
} catch (IOException e) {
LOGGER.error(CommonUtil.ERROR, e);
}
LOGGER.info("Found Hostname: {}", hostname);
return hostname;
}
I have been building my GWT app, using JSONP to communicate with a push server (my own code, based on Netty library). The communication function of the GWT app looks like the following, which sends queries stored in in queryList and processes received data from server via function processDataFromServer. You may notice that right after a fail or a success of communicating, the function calls itself again for keeping connection with server:
ArrayList<String>queryList;
boolean querying = false;
public void queryJsonpServer() {
if (querying) {
return;
}
querying = true;
String jsonString = queryList.isEmpty() ? “” : queryList.remove(0);
String url = postUrl + (!jsonString.isEmpty() ? “?jsonp=" + URL.encodeQueryString(jsonString) : "");
JsonpRequestBuilder jsonp = new JsonpRequestBuilder();
jsonp.setTimeout(60 * 1000);
jsonp.requestObject(url, new AsyncCallback<MyJsonpObject>() {
#Override
public void onFailure(Throwable caught) {
querying = false;
queryJsonpServer();
}
#Override
public void onSuccess(MyJsonpObject o) {
processDataFromServer(o);
querying = false;
queryJsonpServer();
}
});
}
The code works fine if the communication are successes (onSuccess called).
However, once it fails (onFailure called, because of timeout for example), even the function (queryJsonpServer) is called again (I am sure a new query is sent, server receives that query and sends back new data), the function is stuck to receive that new data (onSuccess has not been called since that fail). After a while onFailure called again because of timeout. The problem repeats: query via onFailure, receive nothing, onFailure called again...
Anyone has idea about that problem? Thanks
I'm currently using Topic based communication using JADE. I'm able to register a JADE agent using jade.core.messaging.TopicManagementFEService thereby connecting to the main-container in the same platform.
The details are below:
Main-Container: a simple LAMP/WAMP Server that hosts the Main-Container.
Client: An Android Emulator(testing purpose) to connect to the main-container.
Currently,
Server starts the main-container
Android emulator connects to the Main-container successfully (Agent created along with Topic Mgmt Service enabled)
Server is sending messages based on a specific topic.
But my Android Client is not able to receive this message although the topic registered is the same on both ends!
You can see the code below:
Server Side:
TopicManagementHelper topicHelper = (TopicManagementHelper) getHelper(TopicManagementHelper.SERVICE_NAME);
final AID sensorTopic = topicHelper.createTopic("JADE");
topicHelper.register(sensorTopic);
addBehaviour(new TickerBehaviour(this, TIMER_VALUE_IN_MILLISECONDS) {
private static final long serialVersionUID = -2567778187494378326L;
public void onTick() {
ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
msg.addReceiver(eventTopic);
msg.setContent(eventValue);
myAgent.send(msg);
}
});
Android Side:
// Registering on Android Side as well
TopicManagementHelper topicHelper = (TopicManagementHelper) getHelper(TopicManagementHelper.SERVICE_NAME);
topic = topicHelper.createTopic("JADE"); // See, same topic!
topicHelper.register(topic);
behaviour = new myBehaviour(this, TIMER_VALUE_IN_MILLISECONDS, topic);
addBehaviour(behaviour);
private class myBehaviour extends TickerBehaviour {
private static final long serialVersionUID = 4782913834042415090L;
AID topic;
Agent agent;
MessageTemplate tpl;
public myBehaviour(Agent a, long period, AID topic) {
super(a, period);
this.agent = a;
this.topic = topic;
}
public void onTick() {
tpl = MessageTemplate.MatchTopic(topic);
ACLMessage msg = receive(tpl);
if (msg != null) {
logger.log(Level.INFO, "Agent "+ agent.getLocalName() +
": Message about topic "+ topic.getLocalName() +" received. \n" +
"Content is " + msg.getContent());
data = msg.getContent();
} else {
logger.log(Level.INFO, "In here..."); // Always executes only this code!
block();
}
}
}
Where am I going wrong here? It always executes the else part in the Android side which is obvious to say that message received is NULL!
Never mind. The logic was wrong. The Android-Agent was not identifying itself to the Central-Agent.
I set the Ontology so that the Central Agent is able to identify such message and sends the message accordingly. Now, it is receiving messages!
Self-help works sometimes! ;-)
Receiving topic messages doesn't work correctly with Android up to version 4.3.0 in JADE. Android can send out topic messages but can't receive them. I found this out through my own issues. I've posted more info about it in my own question on stack overflow.
Take a look. JADE Leap Android App unable to receive topic messages