In my JIRA plug-in I have created a WebListener which add a websocket endpoint to the SeverContainer.
The problem is, when I make changes to my plugin and upload it in JIRA, the new code is not executed.
This is because the endpoint is not being deployed again. I get the following exception: Multiple Endpoints may not be deployed to the same path
My weblistener:
#WebListener
public class MyListener implements ServletContextListener {
private static final Logger LOG = LoggerFactory.getLogger(MyListener.class);
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
final ServerContainer serverContainer = (ServerContainer) servletContextEvent.getServletContext()
.getAttribute("javax.websocket.server.ServerContainer");
try {
serverContainer.addEndpoint(MyWebsocket.class); //Generates exception when the plug-in is updated
} catch (DeploymentException e) {
LOG.error("Error adding endpoint to the servercontainer: " + e.getMessage());
}
}
#Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
LOG.error("CONTEXT DESTROYED!");
}
}
My websocket:
#ServerEndpoint(value = "/websocket/{myPathParam}")
public class MyWebsocket {
private static final Logger LOG = LoggerFactory.getLogger(MyWebsocket.class);
#OnOpen
public void onOpen(Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnOpen");
}
#OnClose
public void onClose(Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnClose");
}
#OnMessage
public void onMessage(String message, Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnMessage: " + message);
}
}
Is there a way to remove the endpoint from the servercontainer, so it will be deployed again?
In the specification, I did not find any way to deactivate a websocket. The same applies for ServletContextListeners. They can only be added. So you need a workaround.
I suggest, that you do not replace the MyEndpoint.class, but make it a proxy that will call an implementation. Thus, the endpoint will not be required to re-register and the new proxy class calls the new code, when it is deployed.
So, you can just safely ignore the DeploymentException in your code, because you change the MyWebsocket.class as follows:
#ServerEndpoint(value = "/websocket/{myPathParam}")
public class MyWebsocket {
private static final Logger LOG = LoggerFactory.getLogger(MyWebsocket.class);
#OnOpen
public void onOpen(Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnOpen");
WebSocketImpl impl = ImplementationFactory.get(MyWebsocket.class);
impl.onOpen(session, myPathParam);
}
#OnClose
public void onClose(Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnClose");
WebSocketImpl impl = ImplementationFactory.get(MyWebsocket.class);
impl.onClose(session, myPathParam);
}
#OnMessage
public void onMessage(String message, Session session, #PathParam("myPathParam") String myPathParam) {
LOG.error("OnMessage: " + message);
WebSocketImpl impl = ImplementationFactory.get(MyWebsocket.class);
impl.onMessage(message, session, myPathParam);
}
}
I understand, that this will not really answer your question, but it is a solution how to work around the missing remove options.
One problem is there with this workaround: It will fix your interface, you cannot add parameters in a new version of the plugin.
To enable this, you add another websocket by adding another context listener (V7 has been choosen at random):
#WebListener
public class MyListener_V7 implements ServletContextListener {
private static final Logger LOG = LoggerFactory.getLogger(MyListener_V7.class);
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
final ServerContainer serverContainer = (ServerContainer) servletContextEvent.getServletContext()
.getAttribute("javax.websocket.server.ServerContainer");
try {
serverContainer.addEndpoint(MyWebsocket_V7.class); //Generates exception when the plug-in is updated
} catch (DeploymentException e) {
LOG.error("Error adding endpoint to the servercontainer: " + e.getMessage());
}
}
#Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
LOG.error("CONTEXT DESTROYED!");
}
}
#ServerEndpoint(value = "/websocket_V7/{myPathParam}")
public class MyWebsocket_V7 {
private static final Logger LOG = LoggerFactory.getLogger(MyWebsocket.class);
This will litter the JIRA instance with endpoints until a restart happens. But if you mix the two suggestions, you will only add a new endpoint every once in a while.
Related
I'm using this tutorial and I'm trying to figure out how to get the number of current sessions.
My WebSocketConfig looks like this (copy and paste from the tutorial) :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
I'd like to know the number of sessions inside of this class (again copy and paste):
#Controller
public class GreetingController {
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
}
Is there an easy way to get the number of current sessions(users, connections) to the websocket?
Edit:
Here is my solution:
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
#EventListener
private void onSessionConnectedEvent(SessionConnectedEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
mySet.add(sha.getSessionId());
}
#EventListener
private void onSessionDisconnectEvent(SessionDisconnectEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
mySet.remove(sha.getSessionId());
}
I can now get the number of Sessions with mySet.size() .
You can use SimpUserRegistry and its getUserCount() method instead of handling connections manually.
Example:
#Autowired
private SimpUserRegistry simpUserRegistry;
public int getNumberOfSessions() {
return simpUserRegistry.getUserCount();
}
You can use ApplicationContext events. Every connection, subscription or other action will fire a special event: SessionConnectEvent, SessionConnectedEvent, SessionSubscribeEvent and so on.
Full doc is here. When one of these events fires, you can handle it with your own logic.
Sample code for reference:
#EventListener(SessionConnectEvent.class)
public void handleWebsocketConnectListner(SessionConnectEvent event) {
logger.info("Received a new web socket connection : " + now());
}
#EventListener(SessionDisconnectEvent.class)
public void handleWebsocketDisconnectListner(SessionDisconnectEvent event) {
logger.info("session closed : " + now());
}
I'm using Swarm Wildfly to deploy this application.
Basically I'm making a websocket enabled application.
I'd like to inject a singleton which will be started on the startup which modify the variable result.
Upon accessing the "/rafflethis" link, user will be able to see the result which will be sent via session.
The result is that the roll variable null
This is the class
#Singleton
#Startup
#ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class runMe implements RaffleManager{
private static final Logger LOGGER = Logger.getLogger(runMe.class.getName());
private static String result;
#PostConstruct
public void onStartup() {
System.out.println("Initialization success.");
}
#Schedule(second = "*/10", minute = "*", hour = "*", persistent = false)
public void run() throws Exception{
int i = 0;
while (true) {
Thread.sleep(1000L);
result = UUID.randomUUID().toString().toUpperCase();
i++;
LOGGER.log(Level.INFO, "i : " + i);
}
}
public String getResult() {
return result;
}
}
interface
public interface RaffleManager {
String getResult();
}
And the "/rafflethis"
#ServerEndpoint("/rafflethis")
public class RaffleThis implements Serializable {
#EJB
RaffleManager roll;
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());
private static void sendMessageToAll(String message) {
for (Session s : sessions) {
try {
s.getBasicRemote().sendText(message);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
#OnOpen
public void monitorLuckyDip(Session session) throws Exception {
sessions.add(session);
while(true) {
sendMessageToAll(roll.getResult());
}
}
}
Any lead where should I head from this? Thanks!
Looking at the source, I would probably assume a name issue.
Your singleton bean "runMe" is the actual name of the bean not the interface.
SIDE NOTE: Best practice for class names is capitalize the first letter. RunMe instead of runMe.
#Singleton - without parameters will automatically name your bean for lookup using the bean convention from your class name. Imagine if you implement multiple interface, how does EJB pick the name? So it is just logical to use the class name. E.g. If your classname is TestMe, the ejb name will be testMe.
In your case since your class name is runMe, I would think the bean name will be runMe.
To ensure the lookup will not fail, you can specific the name in #Singleton and #EJB.
#Singleton(name = "runMe")
#Startup
#ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class runMe implements RaffleManager{
Then in your Service end point class
#ServerEndpoint("/rafflethis")
public class RaffleThis implements Serializable {
#EJB(beanName ="runMe")
RaffleManager roll;
The solution is rather hacky but a very simple one indeed. Take a look at the provided diagram.
And here's the code
Logic Implementation:
#Startup
#Singleton
public class RunMe{
private static final Logger LOGGER = Logger.getLogger(RunMe.class.getName());
#Inject
MessageDTO messageDTO;
#PostConstruct
public void onStartup() {
System.out.println("Initialization success.");
}
#Schedule(second = "*/10", minute = "*", hour = "*", persistent = false)
public void run() throws Exception{
//You can also substitute this method with constructor of the class -- removing the #Schedule annotation.
int i = 0;
while (true) {
Thread.sleep(1000L);
messageDTO.setText(UUID.randomUUID().toString().toUpperCase());
i++;
LOGGER.log(Level.INFO, "i : " + i);
}
}
}
MessageDTO:
#Singleton
public class MessageDTO {
private static String text;
public static String getText() {
return text;
}
public static void setText(String text) {
MessageDTO.text = text;
}
}
Websocket Implementation:
#ServerEndpoint("/rafflethis")
public class RaffleThis implements Serializable {
private static final Logger LOGGER = Logger.getLogger(RaffleThis.class.getName());
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());
#Inject
MessageDTO messageDTO;
private static void sendMessageToAll(String message) {
for (Session s : sessions) {
try {
s.getBasicRemote().sendText(message);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
#OnOpen
public void monitorLuckyDip(Session session) throws Exception {
sessions.add(session);
while (true) {
Thread.sleep(200);
sendMessageToAll(messageDTO.getText());
}
}
}
I have Spring service, which is actually actor, it is received info, but I cant pass it to another Spring service, because injection fails.
#Service("mailContainer")
#Scope("prototype")
#Component
public class MailContainer extends UntypedActor {
private final LoggingAdapter LOG = Logging.getLogger(getContext().system(), this);
private Mail value;
private List<Mail> mailList = new ArrayList<Mail>();
private Integer size;
#Autowired
#Qualifier("springService")
private SpringService springService;
//#Autowired
public void setSpringService(SpringService springService) {
this.springService = springService;
}
public MailContainer(Mail value) {
this.value = value;
}
#Override
public void onReceive(Object message) throws Exception {
// LOG.debug("+ MailContainer message: {} ", message);
if (message instanceof Mail) {
value = (Mail) message;
System.out.println("MailContainer get message with id " + value.getId());
System.out.println("With time " + value.getDateSend());
//getSender().tell(value, getSelf()); //heta uxarkum
//this.saveIt(value);
springService.add(value);
}
}
and second service
#Service("springService")
//#Component
#Scope("session")
public class SpringService {
private List<Mail> mailList = new ArrayList<Mail>();
public void add(Mail mail) {
System.out.println("Saving mail from Spring " +mail.getId());
mailList.add(mail);
}
public List<Mail> getMailList() {
return mailList;
}
}
Spring config, this is from akka spring example
#Configuration
//#EnableScheduling
//EnableAsync
#ComponentScan(basePackages = {"com"}, excludeFilters = {
#ComponentScan.Filter(Configuration.class)})
//#ImportResource("classpath:META-INF/spring/spring-data-context.xml")
//#EnableTransactionManagement
//#EnableMBeanExport
//#EnableWebMvc
public class CommonCoreConfig {
// the application context is needed to initialize the Akka Spring Extension
#Autowired
private ApplicationContext applicationContext;
/**
* Actor system singleton for this application.
*/
#Bean
public ActorSystem actorSystem() {
ActorSystem system = ActorSystem.create("AkkaJavaSpring");
// initialize the application context in the Akka Spring Extension
SpringExtProvider.get(system).initialize(applicationContext);
return system;
}
}
So, how I can inject just another Spring service?????????
Based on our discussions, I think it is due to the way you create the MailContainer actor. You aren't using the SpringExtProvider and instead are using Props.create directly. This means that Spring doesn't get the opportunity to perform dependency injection on your new actor.
Try changing this code:
#Override
public void preStart() throws Exception {
System.out.println("Mail collector preStart: {} ");
getContext().actorOf(Props.create(MailContainer.class, result), "one");
}
to use the the SpringExtProvider like this:
#Override
public void preStart() throws Exception {
System.out.println("Mail collector preStart: {} ");
getContext().actorOf(SpringExtProvider.get(getContext().system()).props("mailContainer"), "one");
}
This way you are asking the Spring extension to create the new actor and inject any required dependecnies.
I have an issue/problem with CDI in the next scenario:
Initializator is injected in the ServletContextListener. But after some other "steps" the method startup is invoked:
#WebListener
public class ContextListener implements ServletContextListener {
#Inject
private Initializator initializator;
public void contextInitialized(ServletContextEvent event) {
ServletContext servletContext = (ServletContext) event.getSource();
String contextPath = ((ServletContext) event.getSource()).getContextPath();
String serverName = servletContext.getInitParameter("SERVER_NAME");
initializator.startup(serverName);
System.out.println("ServletContext " + contextPath + " stated.");
}
public void contextDestroyed(ServletContextEvent event) {
String contextPath = ((ServletContext) event.getSource()).getContextPath();
System.out.println("ServletContext " + contextPath + " stopped.");
}
}
The repository is successful injected in the initializator:
public class Initializator {
#Inject
private ChannelRepository repo;
public String serverName;
public void startup(String aServerName) {
this.serverName = aServerName;
initAll();
}
private void initAll() {
List<Channel> channels = repo.getChannels();
for (Channel channel : channels) {
channel.start();
}
}
}
The repository retrieves the data and instantiates channels:
public class ChannelRepository {
public List<Channel> getChannels() {
List<Channel> channels = new ArrayList<Channel>();
// ...some db access via jdbc (or jpa)
channels.add(new Channel("dummy", 8080));
return channels;
}
}
The channel needs a Logger:
public class Channel extends Thread {
#Inject
private Logger logger;
public String name;
public int port;
public Channel(String aName, int aPort) {
this.name = aName;
this.port = aPort;
}
#Override
public void run() {
logger.log("Channel " + name + " is running in port " + port);
// ...some other things to do
}
}
How to avoid the manual creation of Channel instances?
The problem is done because the startup method in Initializator is invoked after the instance construction.
How to manage this type of "deferred" injections?
Avoiding the manual creation of instances of Channel with
new Channel()
is fairly easy.
First we need a default constructor in class Channel and setters for channels attributes.
Then you must add this to your Channel Repo
#Inject
Instances<Channel> channelInstances;
and in your repo method change from
channels.add(new Channel("dummy", 8080));
to
Channel channel = channelInstances.get();
channel.setPort(8080);
channel.setName("dummy");
channels.add(channel);
A small hint:
If it is possible to do, don't let Channel extend Thread, but do the following
final Channel channel = channelInstances.get();
channel.setPort(8080);
channel.setName("dummy");
channels.add(new Thread() {
#Override
public void run() {
channel.doTheChannelStuff();
channelInstances.destroy(channel);
}
}
Why should you do this:
Under some circumstances a memory leak will be introduced when doing it in the way you are trying to use it. (Had a similiar issue at work) This has something to do with dependent scoped (default) dependencies and "manual" creation of new instances.
This is probably obvious, but I am new to this paradigm. I create a Jetty Server and register my websocket class as follows:
Server server = new Server(8080);
WebSocketHandler wsHandler = new WebSocketHandler()
{
#Override
public void configure(WebSocketServletFactory factory)
{
factory.register(MyEchoSocket.class);
}
};
server.setHandler(wsHandler);
The websocket receives messages fine. I would like to also be able to send messages out from the server without having first received a message from the client. How do I access the MyEchoSocket instance that's created when the connection opens? Or, more generally, how do I send messages on the socket outside of the onText method in MyEchoSocket?
Two common techniques, presented here in a super simplified chatroom concept.
Option #1: Have WebSocket report back its state to a central location
#WebSocket
public class ChatSocket {
public Session session;
#OnWebSocketConnect
public void onConnect(Session session) {
this.session = session;
ChatRoom.getInstance().join(this);
}
#OnWebSocketMessage
public void onText(String message) {
ChatRoom.getInstance().writeAllMembers("Hello all");
}
#OnWebSocketClose
public void onClose(int statusCode, String reason) {
ChatRoom.getInstance().part(this);
}
}
public class ChatRoom {
private static final ChatRoom INSTANCE = new ChatRoom();
public static ChatRoom getInstance()
{
return INSTANCE;
}
private List<ChatSocket> members = new ArrayList<>();
public void join(ChatSocket socket)
{
members.add(socket);
}
public void part(ChatSocket socket)
{
members.remove(socket);
}
public void writeAllMembers(String message)
{
for(ChatSocket member: members)
{
member.session.getRemote().sendStringByFuture(message);
}
}
public void writeSpecificMember(String memberName, String message)
{
ChatSocket member = findMemberByName(memberName);
member.session.getRemote().sendStringByFuture(message);
}
public ChatSocket findMemberByName(String memberName)
{
// left as exercise to reader
}
}
Then simply use the central location to talk to the websockets of your choice.
ChatRoom.getInstance().writeSpecificMember("alex", "Hello");
// or
ChatRoom.getInstance().writeAllMembers("Hello all");
Option #2: Have WebSocket be created manually with WebSocketCreator
#WebSocket
public class ChatSocket {
public ChatRoom chatroom;
public ChatSocket(ChatRoom chatroom)
{
this.chatroom = chatroom;
}
#OnWebSocketConnect
public void onConnect(Session session) {
chatroom.join(this);
}
#OnWebSocketMessage
public void onText(String message) {
chatroom.writeAllMembers(message);
}
#OnWebSocketClose
public void onClose(int statusCode, String reason) {
chatroom.part(this);
}
}
public class ChatCreator implements WebSocketCreator
{
private ChatRoom chatroom;
public ChatCreator(ChatRoom chatroom)
{
this.chatroom = chatroom;
}
public Object createWebSocket(UpgradeRequest request,
UpgradeResponse response)
{
// We want to create the Chat Socket and associate
// it with our chatroom implementation
return new ChatSocket(chatroom);
}
}
public class ChatHandler extends WebSocketHandler
{
private ChatRoom chatroom = new ChatRoom();
#Override
public void configure(WebSocketServletFactory factory)
{
factory.setCreator(new ChatCreator(chatroom));
}
}
At this point you can use the same techniques as above to talk to the websockets of your choice.