CDI deferred injection is not working - java

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.

Related

Use Dropwizard configuration in a method that establishes a connection to a MongoDB database

I am coding Dropwizard micro-services that fetch data in a MongoDB database. The micro-services work fine but I'm struggling to use in my DAO the configuration coming from my Dropwizard configuration Java class. Currently I have
public class XDAO implements IXDAO {
protected DB db;
protected DBCollection collection;
/* singleton */
private static XDAO instance;
/* Get singleton */
public static synchronized XDAO getSingleton(){
if (instance == null){
instance = new XDAO();
}
return instance;
}
/* constructor */
public XDAO(){
initDatabase();
initDatabaseIndexes();
}
private void initDatabase(){
MongoClient client = null;
try {
client = new Mongo("10.126.80.192",27017);
db = client.getDB("terre");
//then some other code
}
catch (final MongoException e){
...
}
catch (UnknownHostException e){
...
}
}
}
I want to unhard-code the three arguments in these two lines :
client = new Mongo("10.126.80.192", 27017);
db = client.getDB("terre");
My MongoConfiguration Java class is :
public class MongoConfiguration extends Configuration {
#JsonProperty
#NotEmpty
public String host;
#JsonProperty
public int port = 27017;
#JsonProperty
#NotEmpty
public String db_name;
public String getMongohost() {
return host;
}
public void setMongohost(String host) {
this.host = host;
}
public int getMongoport() {
return port;
}
public void setMongoport(int port) {
this.port = port;
}
public String getDb_name() {
return db_name;
}
public void setDb_name(String db_name) {
this.db_name = db_name;
}
}
My Resource class that uses the DAO is :
#Path("/mongo")
#Produces(MediaType.APPLICATION_JSON)
public class MyResource {
private XDAO xDAO = XDAO.getSingleton();
private String mongohost;
private String db_name;
private int mongoport;
public MyResource(String db_name, String mongohost, int mongoport) {
this.db_name = db_name;
this.mongohost = mongohost;
this.mongoport = mongoport;
}
public MyResource() {
}
#GET
#Path("/findByUUID")
#Produces(value = MediaType.APPLICATION_JSON)
#Timed
public Entity findByUUID(#QueryParam("uuid") String uuid) {
return xDAO.findByUUid(uuid);
}
}
And in my application class there is
#Override
public void run(final MongoConfiguration configuration, final Environment environment) {
final MyResource resource = new MyResource(configuration.getDb_name(), configuration.getMongohost(), configuration.getMongoport());
environment.jersey().register(resource);
}
To solve my problem I tried many things. The last thing I tried was to add these four fields in my XDAO
private String mongohost;
private String db_name;
private int mongoport;
private static final MongoConfiguration configuration = new MongoConfiguration();
Coming with this piece of code in the constructor of the XDAO:
public XDAO(){
instance.mongohost = configuration.getMongohost();
instance.mongoport = configuration.getMongoport();
instance.db_name = configuration.getDb_name();
/* then like before */
initDatabase();
initDatabaseIndexes();
}
When I try this I have a null pointer exception when my initDatabase method is invoked : mongoHost and db_name are null
The problem is that you are creating a new configuration in your XDAO with private static final MongoConfiguration configuration = new MongoConfiguration(); instead of using the config from Dropwizard's run method.
When you do this, the fields host and db_name in the new configuration are null, which is why you are getting the NPE when instantiating XDAO
You need to pass the instance of MongoConfiguration that you get from Dropwizard in your application class to your XDAO, ideally when the singleton XDAO is created so it has non-null values for db_name and host
This code below part of the problem - you are creating the singleton without giving XDAO the MongoConfiguration configuration instance.
public class XDAO implements IXDAO {
//... snip
/* Get singleton */
public static synchronized XDAO getSingleton(){
if (instance == null){
instance = new XDAO(); // no configuration information is included!
}
return instance;
}
/* constructor */
public XDAO(){
initDatabase(); // this call needs db_name & host but you haven't set those yet!!
initDatabaseIndexes();
}
I recommend you modify your application class to create XDAO along the lines of this:
#Override
public void run(final MongoConfiguration configuration, final Environment environment) {
XDAO XDAOsingleton = new XDAO(configuration);
XDAO.setSingletonInstance(XDAOsingleton); // You need to create this static method.
final MyResource resource = new MyResource(configuration.getDb_name(), configuration.getMongohost(), configuration.getMongoport()); // MyResource depends on XDAO so must be created after XAO's singleton is set
environment.jersey().register(resource);
}
You may also need to take initDatabase() etc out of XDAO's constructor depending on if you keep public static synchronized XDAO getSingleton()
I also recommend you change the constructor of MyResource to public MyResource(XDAO xdao). The resource class doesn't appear to need the configuration information, and it is better to make the dependency on an XDAO explicit (you then also don't need to keep the XDAO singleton in a static field inside XDAO's class).
To get MongoDB integrated in a simple way to Dropwizard, please try and use MongoDB Managed Object. I will explain this in 3 simple steps:
Step 1: Create a simple MongoManged class:
import com.mongodb.Mongo;
import io.dropwizard.lifecycle.Managed;
public class MongoManaged implements Managed {
private Mongo mongo;
public MongoManaged(Mongo mongo) {
this.mongo = mongo;
}
#Override
public void start() throws Exception {
}
#Override
public void stop() throws Exception {
mongo.close();
}
}
Step 2: Mention MongoDB Host, Port, DB Name in a config yml file:
mongoHost : localhost
mongoPort : 27017
mongoDB : softwaredevelopercentral
Step 3: Bind everything together in the Application Class:
public class DropwizardMongoDBApplication extends Application<DropwizardMongoDBConfiguration> {
private static final Logger logger = LoggerFactory.getLogger(DropwizardMongoDBApplication.class);
public static void main(String[] args) throws Exception {
new DropwizardMongoDBApplication().run("server", args[0]);
}
#Override
public void initialize(Bootstrap<DropwizardMongoDBConfiguration> b) {
}
#Override
public void run(DropwizardMongoDBConfiguration config, Environment env)
throws Exception {
MongoClient mongoClient = new MongoClient(config.getMongoHost(), config.getMongoPort());
MongoManaged mongoManaged = new MongoManaged(mongoClient);
env.lifecycle().manage(mongoManaged);
MongoDatabase db = mongoClient.getDatabase(config.getMongoDB());
MongoCollection<Document> collection = db.getCollection(config.getCollectionName());
logger.info("Registering RESTful API resources");
env.jersey().register(new PingResource());
env.jersey().register(new EmployeeResource(collection, new MongoService()));
env.healthChecks().register("DropwizardMongoDBHealthCheck",
new DropwizardMongoDBHealthCheckResource(mongoClient));
}
}
I have used these steps and written a blog post and a sample working application code is available on GitHub. Please check: http://softwaredevelopercentral.blogspot.com/2017/09/dropwizard-mongodb-tutorial.html

Deep Beans initialization with thread-specific data in Spring framework

Spring's dependency injection mechanism looks convenient because I don't have to create classes manually from root class and pass them to deeper classes. But what if I have 3 separate threads which do the same job and have some thread-specific data which should be spread through the class hierarchy?
Consider the following App:
#SpringBootApplication
public class App {
public static void main(String[] args) throws IOException {
Properties props = new Properties();
props.load(App.class.getResourceAsStream("/application.properties"));
String[] domains = props.getProperty("domains").split(",");
ConfigurableApplicationContext context = SpringApplication.run(App.class);
ConfigurableListableBeanFactory factory = context.getBeanFactory();
for(String domain : domains) {
DomainListener listener = factory.getBean(DomainListener.class);
listener.setDomain(domain);
listener.start();
}
}
}
#Service
#Scope("prototype")
public class DomainListener extends Thread {
#Autowired
EventPublisher publisher;
String domain;
public void setDomain(String domain) {
this.domain = domain;
}
#Override
public void run() {
System.out.println("starting '" + domain + "' domain thread...");
publisher.setDomain(domain); // <-- is this the best way to initialize bean?
// some processing
publisher.publish("Some event");
}
}
#Component
#Scope("prototype")
public class EventPublisher {
String domain;
public void setDomain(String domain) {
this.domain = domain;
}
public void publish(String msg) {
System.out.println("publishing '" + msg + "' to '" + domain + "' domain");
}
}
application.properties file content
domains = domain1.example.com,domain2.example.com,domain3.example.com
DomainListener and EventPublisher classes can't be initialized with domain value automatically by Spring, thus I have to call setDomain method and pass the domain value manually, first during DomainListener thread construction, then in DomainListener.run() to initialize EventPublisher with domain value. Would I need to pass the value even deeper, for example from EventPublisher to some other class, say MessageBuilder (let's assume it requires domain value), then I would have to call MessageBuilder.setDomain() instance method from EventPublisher constructor (as it is the same domain thread).
Can this be improved somehow?

Inject EJB in #serverEndpoint class results in NullPointerException

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());
}
}
}

Remove endpoint from ServerContainer inside WebListener

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.

Could not inject Spring service into akka service

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.

Categories