Spring Framework MVC 4, JPA, Atmosphere-jersey, Real time Notifications - java

I need to make push notifications on our webapp. Currently we built the app using Spring Framework MVC in Maven using Spring Tool Suite. All features are done but we need to add this real time notification feature like in facebook using Atmosphere-jersey.
I did a thorough search everywhere in the web before posting this btw. Soooooooo...
MAIN PROBLEM: Spring Framework MVC 4 is separate instance in Atmosphere.(Instance or whatever the term is) So I cant Autowire the service from spring to Atmosphere service. Please give me concepts or ideas on what to do here using this libraries.
There's no problem in the frontend. Just got stucked on this spring and atmosphere thingy.
Tried the answers in this question but didn't work: atmosphere + spring + autowired problems
Approach 1: #Configurable, #Autowired but service is still null.
Searches did:
Using #Configurable which needs enabled LTW(Load Time Weaving), but got stucked when whwre -javaagent has a bug or whatever in vFabric TC Server. - http://forum.spring.io/forum/spring-projects/springsource-tool-suite/116703-vmware-vfabric-tc-server-does-not-save-launch-configuration
#ManagedService(path = "/notification/{channel: [a-zA-Z][a-zA-Z_0-9]*}")
#Singleton
#Configurable(dependencyCheck=true,autowire=Autowire.BY_TYPE)
public final class NotificationChannelController {
private final Logger logger = LoggerFactory.getLogger(NotificationChannelController.class);
private final ConcurrentHashMap<String, String> users = new ConcurrentHashMap<String, String>();
private final static String notifChannel = "/notification/";
#Autowired
private NotificationService notificationService;
#PathParam("channel")
private String channel;
#Inject
private BroadcasterFactory factory;
#Inject
private AtmosphereResourceFactory resourceFactory;
#Inject
private MetaBroadcaster metaBroadcaster;
#Ready(encoders = {NotificationProtocolCodec.class})
#DeliverTo(DeliverTo.DELIVER_TO.ALL)
public void onReady(final AtmosphereResource r) {
logger.info("Browser {} connected.", r.uuid());
return;
}
#Disconnect
public void onDisconnect(AtmosphereResourceEvent event) {
users.values().remove(event.getResource().uuid());
if (event.isCancelled()) {
// We didn't get notified, so we remove the user.
logger.info("Browser {} unexpectedly disconnected", event.getResource().uuid());
} else if (event.isClosedByClient()) {
logger.info("Browser {} closed the connection", event.getResource().uuid());
}
}
#Message(encoders = {NotificationProtocolCodec.class}, decoders = {NotificationProtocolCodec.class})
public NotificationProtocol onMessage(NotificationProtocol message) throws IOException {
if (!users.containsKey(message.getConciergeId())) {
users.put(message.getConciergeId(), message.getUuid());
}
if (message.getMessage().contains("disconnecting")) {
users.remove(message.getConciergeId());
return new NotificationProtocol(message.getConciergeId(), "", "");//" disconnected from room " + channel);
}
logger.info("{} just send {}", message.getConciergeId(), message.getMessage());
return new NotificationProtocol(message);
}
#Message(encoders = {NotificationProtocolCodec.class}, decoders = {NotificationCodec.class})
public void onPrivateNotification(NotificationComment notificationComment) throws IOException {
List<NotificationProtocol> npList = notificationService.saveAndNotifyUsers(notificationComment);
for(NotificationProtocol np : npList){
if (userUUID != null) {
// Retrieve the original AtmosphereResource
AtmosphereResource r = resourceFactory.find(userUUID);
if (r != null) {
factory.lookup(notifChannel + channel).broadcast(np, r);
}
}
}
}
Service to Autowire
#Service
#Transactional
public class NotificationServiceImpl implements NotificationService{
}
web.xml
<!-- Atmosphere -->
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet>
<description>AtmosphereServlet</description>
<servlet-name>AtmosphereServlet</servlet-name>
<servlet-class>org.atmosphere.cpr.AtmosphereServlet</servlet-class>
<init-param>
<param-name>org.atmosphere.cpr.broadcasterLifeCyclePolicy</param-name>
<param-value>EMPTY_DESTROY</param-value>
</init-param>
<init-param>
<param-name>org.atmosphere.cpr.broadcaster.shareableThreadPool</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>org.atmosphere.cpr.recoverFromDestroyedBroadcaster</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>org.atmosphere.websocket.messageContentType</param-name>
<param-value>application/json</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AtmosphereServlet</servlet-name>
<url-pattern>/notification/*</url-pattern>
</servlet-mapping>
application-context.xml
<context:component-scan base-package="packagefolder" />
<context:annotation-config />
<context:load-time-weaver aspectj-weaving="on"/>
pom.xml
<!-- Atmosphere -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-jersey</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.atmosphere.client</groupId>
<artifactId>javascript</artifactId>
<version>2.2.4</version>
<type>war</type>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
<!-- Spring ORM Hibernate -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- #Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
Just say so if you want more of the pom.xml
Tried this but no good
-javaagent:"pathto/spring-instrument-4.0.6.RELEASE.jar"
Approach 2: For now, trying to get the users connected to NotificationChannelController from a spring controller. But still null even if clients conneted to the managedservice path when accessing from spring controller. I know its not the right way but have to do some tricks to make this work.
Controller:
#Controller
#RequestMapping("secured/*")
public class BaseController {
private static final Logger LOGGER = LoggerFactory
.getLogger(BaseController.class);
#RequestMapping(value = "test", method = RequestMethod.GET)
#ResponseBody
public void testMethod() throws Exception {
System.out.println("AAAAAAAAAAAAAAAAAA");
NotificationChannelController ncc = new NotificationChannelController();
ncc.show();
}
}
NotificationChannelController
#ManagedService(path = "/notification/{channel: [a-zA-Z][a-zA-Z_0-9]*}")
#Singleton
#Configurable(preConstruction=true,dependencyCheck=true,autowire=Autowire.BY_TYPE)
public final class NotificationChannelController {
private final Logger logger = LoggerFactory.getLogger(NotificationChannelController.class);
private final ConcurrentHashMap<String, String> users = new ConcurrentHashMap<String, String>();
public void show(){
System.out.println("A");
for(String key : users.keySet()){
System.out.println(key + " " + users.get(key));
}
System.out.println("B");
for(String s : getRooms(factory.lookupAll())){
System.out.println(s);
}
System.out.println("C");
}
}
Thanks for taking time on reading this and I hope someone can shed light on this. Thanks!

did you read https://github.com/Atmosphere/atmosphere/wiki/Configuring-Atmosphere's-Classes-Creation-and-Injection You needs atmosphere-spring extension if you want to make it work. Take a look a the code here
https://github.com/Atmosphere/atmosphere-extensions/tree/master/spring/modules/src/main/java/org/atmosphere/spring
-- Jeanfrancois

has been a while since implementing Java but don't you need some kind of bean declaration as configuration to get a been autowired?
In Example for a cxf tutorial i autowired a Dao to a service the bean itself is defined in the beans.xml.
maybe you can have a look and good luck!

Related

Cannot persist via Hibernate ORM with Panache using rubenlagus/TelegramBots

I'm using Quarkus 2.15.1 and rubenlagus/TelegramBots 6.3.0
My pom.xml dependencies are:
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>6.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
I've configured jdbc URL, username & password as well.
The problem is when I'm trying to persist an object (via Active Record or Repository pattern) there is an exception.
The code snippet is:
#ApplicationScoped
public class VoteBot extends TelegramLongPollingBot {
#Override
public void onUpdateReceived(Update update) {
if (update.hasMessage() && update.getMessage().hasText()) {
long chatId = update.getMessage().getChatId();
String messageText = update.getMessage().getText();
if (messageText.matches("^[A-Za-z\\s]+$")) {
Participant participant = new Participant();
participant.setChatId(chatId);
participant.setName(messageText);
// Some code
Participant.persist(participant); // Here is the exceptional line goes
// Some code
}
}
Startup class looks like:
#ApplicationScoped
public class Application {
void onStart(#Observes StartupEvent ev) {
try {
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
telegramBotsApi.registerBot(new VoteBot());
} catch (TelegramApiException e) {
e.printStackTrace();
}
}
}
The exception message is:
2022-12-25 02:22:19,727 ERROR [org.tel.tel.upd.DefaultBotSession] (ckorovoda_vote_bot Telegram Executor) Cannot use the EntityManager/Session because neither a transaction nor a CDI request context is active. Consider adding #Transactional to your method to automatically activate a transaction, or #ActivateRequestContext if you have valid reasons not to use transactions.: javax.enterprise.context.ContextNotActiveException: Cannot use the EntityManager/Session because neither a transaction nor a CDI request context is active. Consider adding #Transactional to your method to automatically activate a transaction, or #ActivateRequestContext if you have valid reasons not to use transactions.
at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.acquireSession(TransactionScopedSession.java:106)
at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.contains(TransactionScopedSession.java:325)
at org.hibernate.engine.spi.SessionLazyDelegator.contains(SessionLazyDelegator.java:710)
at org.hibernate.Session_5b93bee577ae2f8d76647de04cfab36afbf52958_Synthetic_ClientProxy.contains(Unknown Source)
at io.quarkus.hibernate.orm.panache.common.runtime.AbstractJpaOperations.persist(AbstractJpaOperations.java:100)
at io.quarkus.hibernate.orm.panache.common.runtime.AbstractJpaOperations.persist(AbstractJpaOperations.java:96)
at io.quarkus.hibernate.orm.panache.common.runtime.AbstractJpaOperations.persist(AbstractJpaOperations.java:112)
at io.quarkus.hibernate.orm.panache.PanacheEntityBase.persist(PanacheEntityBase.java:756)
at store.ckorovoda.Participant.persist(Participant.java)
at store.ckorovoda.VoteBot.onUpdateReceived(VoteBot.java:74)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.telegram.telegrambots.meta.generics.LongPollingBot.onUpdatesReceived(LongPollingBot.java:27)
at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$HandlerThread.run(DefaultBotSession.java:317)
I've tried adding #Transactional, #ActivateRequestContext and changing bean scopes. I've also tried to use EntityManager and quarkus-narayana-jta. However, I'm pretty sure it's all connected with this 3rd-party lib for Telegram.
I'm stuck with it and will appreciate any recommendation or hints for resolving that stuff.
Thank you!

Java EntityManager null with #PersistenceContext

I'm using Wildfly 10, Jersey, and injecting dependencies with #Inject. I have DAO and Service interfaces with their implementations declared in a CustomBinder. The injections work well, but the EntityManager is injected null with the #PersistenceContext annotation. I'm using MySQL and the datasource test-connection is working.
API Rest class
#Path("/account")
public class CuentaServiceRS {
#Inject
private ICuentaService cuentaService;
#GET
#Produces(MediaType.APPLICATION_JSON)
public Cuenta getCuenta() {
return cuentaService.getCuentas().get(0);
}
}
The implementation of ICuentaService
#Stateless
public class CuentaServiceImpl implements ICuentaService {
#Inject
private ICuentaDAO cuentaDAO;
#Override
public List<Cuenta> getCuentas() {
List<Cuenta> cuentas = cuentaDAO.getAllCuentas();
cuentas;
}
}
The implementation of CuentaDAO
#Stateless
public class CuentaDAOImpl implements ICuentaDAO {
#PersistenceContext(unitName = "altitudePU")
protected EntityManager em;
#Override
public List<Cuenta> getAllCuentas() {
CriteriaQuery<Cuenta> cq = em.getCriteriaBuilder().createQuery(Cuenta.class);
/...
return resultlist;
}
}
My persistence-unit in persistence.xml
<persistence-unit name="altitudePU">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:jboss/datasources/AltitudePU</jta-data-source>
<class>ar.com.olx.domain.Cuenta</class>
<properties>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
</properties>
</persistence-unit>
The servlet configured on web.xml
<servlet>
<servlet-name>altitudeservlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>ar.com.villat.bind.ApplicationJaxRS</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
My custom AplicationJaxRS which extendes from ResourceConfig
public class ApplicationJaxRS extends ResourceConfig {
public ApplicationJaxRS(){
register(new CustomBinder());
packages(true, "ar.com.olx");
}
}
The CustomBinder
public class CustomBinder extends AbstractBinder {
#Override
protected void configure() {
bind(CuentaServiceImpl.class).to(ICuentaService.class);
bind(CuentaDAOImpl.class).to(ICuentaDAO.class);
}
}
And finally, my pom.xml dependencies related to this post
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.22.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.5.6-Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ejb</groupId>
<artifactId>jboss-ejb-api_3.2_spec</artifactId>
<version>1.0.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
<scope>provided</scope>
</dependency>
If you need more information, please tell me.
As you are using a full stack JavaEE server you can simplify this a lot.
If you have not added a MySQL datasource to WildFly then you can do it like this:
WildFly Datasource Setup
Create a file called deploy-mysql-ds.cli with the following content:
# Execute offline
embed-server --server-config=standalone.xml
deploy $HOME/.m2/repository/mysql/mysql-connector-java-5.1.39.jar
# Add the application datasource
data-source add \
--name=AltitudeDS \
--driver-name=mysql-connector-java-5.1.39.jar \
--connection-url=jdbc:mysql://localhost:3306/altitudeDB \
--jndi-name=java:jboss/datasources/AltitudePU \
--user-name=$USER_NAME \
--password=$PASSWORD
Replace $HOME, $USER_NAME and $PASSWORD with real values
Configure the data source in Wildfly by executing it like:
$JBOSS_HOME/bin/jboss-cli.sh --file="deploy-mysql-ds.cli"
You only need to do this once.
Note that the --jndi-name matches the <jta-data-source>...</jta-data-source> in
your persistence.xml file.
You do not need the mysql-connector-java-5.1.39.jar in your maven dependencies because it has been added to the server directly. There are other solutions that involve adding the jar as a "module"
but I find this way to be much simpler.
Amend Application
Amend your application as follows:
Remove all the redundant dependencies:
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
Remove the AplicationJaxRS and CustomBinder classes and add a JAXRSConfiguration class:
/**
* Configures a JAX-RS endpoint
*/
#ApplicationPath("resources")
public class JAXRSConfiguration extends Application {
}
Remove the altitudeservlet from the web.xml. If that is the only thing in it then completely remove the whole file.
Test it
You should be able to deploy this web application and access it from:
http://localhost:8080/altitude-webapp/resources/account
where altitude-webapp is the name of your WAR file.

Running a Jersey application using GlassFish in IntelliJ gives requested resource is not available error

I have created a small Jersey application which has the following resource class.
#Path("/tests")
public class TestController {
#GET
#Produces("application/json")
public Response testJob(#PathParam("testName") String testName) {
return Response.ok("test").build();
}
}
Below is my application class.
#ApplicationPath("/")
public class ApplicationConfig extends Application {
// All request scoped resources and providers
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(TestController.class);
return classes;
}
// all singleton resources and providers
#Override
public Set<Object> getSingletons() {
Set<Object> singletons = new HashSet<>();
return singletons;
}
}
Below is my running configuration in intellij.
I just downloaded the glass fish and did no external configurations. I searched online for the default user name password for domain and gave it them as admin and admin respectively. But when I run the application using the URL,
http://localhost:8080/test-processor/tests
I am getting a 404 error saying the requested resource is not found. My war module name is test-processor. I have tried the url http://localhost:8080/tests with no luck as well. What am I doing wrong here? Any help would be much appreciated. Below is the list of dependencies I have added in my pom xml.
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-bundle</artifactId>
<version>1.18.3</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>2.25.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-guice</artifactId>
<version>1.19.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>

Need a way to generate JSON from List<Map<String,String> in JAX RS service. jackson2

I have the following in my POM.xml
Updated complete pom:
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.19</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.19</version>
</dependency>
<!-- <dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.19</version>
</dependency> -->
<dependency>
<groupId>org.mvel</groupId>
<artifactId>mvel2</artifactId>
<version>2.2.1.Final</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.drill.exec</groupId>
<artifactId>drill-jdbc-all</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
I wanted to generate an JSON from List<Map<String,String>>. I always end up having MessageBodyWriter not found for media type=application/json, type=class java.util.LinkedHashMap, genericType=jav a.util.Map<String,String>
I even tried with simple map
#GET
#Path("/test")
#Produces(MediaType.APPLICATION_JSON)
public Map<String, String> getMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("some key", "some value");
return map;
}
even this produces the same error message.
UPDATED EXCEPTION STACK TRACE:
Jul 29, 2015 9:23:29 AM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [CustomerDataService] in context with path [/CustomerDataService] threw exception
[org.glassfish.jersey.server.ContainerException: java.lang.AbstractMethodError: com.fasterxml.jackson.jaxrs.json.Jackson
JaxbJsonProvider.isWriteable(Ljava/lang/Class;Ljava/lang/reflect/Type;[Ljava/lang/annotation/Annotation;Ljavax/ws/rs/cor
e/MediaType;)Z] with root cause
java.lang.AbstractMethodError: com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.isWriteable(Ljava/lang/Class;Lja
va/lang/reflect/Type;[Ljava/lang/annotation/Annotation;Ljavax/ws/rs/core/MediaType;)Z
at org.glassfish.jersey.message.internal.MessageBodyFactory.isWriteable(MessageBodyFactory.java:1158)
at org.glassfish.jersey.message.WriterModel.isWriteable(WriterModel.java:86)
at org.glassfish.jersey.message.internal.MessageBodyFactory._getMessageBodyWriter(MessageBodyFactory.java:798)
at org.glassfish.jersey.message.internal.MessageBodyFactory.getMessageBodyWriter(MessageBodyFactory.java:756)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterc
eptorExecutor.java:241)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:106)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInter
ceptor.java:86)
MOXy doesn't handle maps well as it uses JAXB under the hood, and JAXB sucks with Maps. Use Jackson instead. Get rid of the MOXy dependency. MOXy is the default, so if it is on the classpath, it will be used over all others. Once you take it out, Jackson will be used.
UPDATE
So the drill-jdbc-all actually repackages both Jackson 1.x and Jackson 2.x. You can exclude them from the jersey-media-json-jackson depedency, since drill already includes them.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.19</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-base</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
UPDATE 2
I guess the above still doesn't work. One way I was able to get to work is adding jackson-jaxrs-json-provider (you also need to take out jersey-media-json-jackson and the jersey-media-moxy).
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.drill.exec</groupId>
<artifactId>drill-jdbc-all</artifactId>
<version>1.1.0</version>
</dependency>
The key to this is that it must be declared before the apache-jdbc-drill. Yea it seems more of a work around that a solution. But the fact that drill packages the dependency is a problem. When Jersey scans, it will will add ours first, and ignore drill's.
You then need to add the provider to your Jersey config
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
</param-value>
</init-param>
I mean I haven't tested this 10,000, but for the 10 to 20 times I did tested, the ordering of the dependencies was the deciding factor for whether or not it would work. Again, like I said, seems like a work-around.
And the add it to the Jersey config
You cannot serialize collections and maps. Create a container class.
For example:
#XmlRootElement
public class Container {
public Map<String, String> data;
}
public Container getMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("some key", "some value");
// wrap content int container object
Container container = new Container();
container.data = map;
return container ;
}
The reason for this issue is, that XML always has to have a single root element.

Integrating Jersey 2 and Spring with Java Based Configuration

I am using Jersey 2.10 and jersey-spring3 and Spring 4.
I want to achieve DI(basically services) in jersey resources as well as in other places and want to create Spring Beans through Java Configuration.
Currently,I am not able to find out any way to do this.
Any idea how to do this?
my web.xml looks like this
<web-app>
<display-name>Restful Web Application</display-name>
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>
org.glassfish.jersey.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>
jersey.config.server.provider.packages
</param-name>
<param-value>com.xyz</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/application-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet-mapping>
<servlet-name>jersey-serlvet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
web-app:
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>xxx.xxx.configuration.ApplicationConfiguration</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>SpringApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>xxx.xxx.controllers.HelloController</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringApplication</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
JavaBased Configuration:
#Configuration
public class ApplicationConfiguration {
#Bean
HelloService helloService () {
return new HelloServiceImpl();
}
}
and simple controller:
#Component
#Path("/helloController")
public class HelloController {
#Autowired
#Qualifier("helloService")
private HelloService helloService ;
#GET
#Path("/hello")
public String hello() {
helloService.service();
}
}
for testing:
localhost:8080/[AppName]/helloController/hello
remember about excluding old Spring dependencies you may have some conflicts if you don't. You can do this same as on the example below or through DependencyManagement.
<dependencies>
<!-- Jersey -->
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>2.11</version>
<exclusions>
<exclusion>
<artifactId>spring-context</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-beans</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-core</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-web</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>jersey-server</artifactId>
<groupId>org.glassfish.jersey.core</groupId>
</exclusion>
<exclusion>
<artifactId>
jersey-container-servlet-core
</artifactId>
<groupId>org.glassfish.jersey.containers</groupId>
</exclusion>
<exclusion>
<artifactId>hk2</artifactId>
<groupId>org.glassfish.hk2</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- Spring 4 dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
</dependencies>
Old Fashioned way:
Since you have already initialized the ContextLoaderListener a simple trick is to use the WebApplicationContext to retrieve your beans at any application point:
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
SomeBean someBean = (SomeBean) ctx.getBean("someBean");
Jersey Support:
Or you can use the annotation based discovery, since Jersey has already support for Spring DI. You have to register your beans under your main application entry point. That entry point, in below example will be some.package.MyApplication, should be provided as an <init-param> of the servlet container:
<servlet>
<servlet-name>SpringApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>some.package.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Register you beans in your application:
package some.package;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
public class MyApplication extends ResourceConfig {
public MyApplication () {
register(RequestContextFilter.class);
register(SomeBean.class);
// ...
}
}
Here you can take a look to a ready to run example from Jersey Git repo.
Here is something that I found starting from various tutorials. Combined with other answers you should have a complete example.
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ctx);
ServletRegistration.Dynamic servlet = servletContext.addServlet("jersey-serlvet", new SpringServlet());
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
For those trying to do it with Java config:
public static void main(String[] args) throws IOException {
HttpServer server = new HttpServer();
NetworkListener listener = new NetworkListener("grizzly2", "localhost", 2088);
server.addListener(listener);
WebappContext ctx = new WebappContext("ctx","/");
final ServletRegistration reg = ctx.addServlet("spring", new SpringServlet());
reg.addMapping("/*");
ctx.addContextInitParameter( "contextClass", "org.springframework.web.context.support.AnnotationConfigWebApplicationContext" );
ctx.addContextInitParameter( "contextConfigLocation", "com.example.AppConfig" );
ctx.addListener( "org.springframework.web.context.ContextLoaderListener" );
ctx.addListener("org.springframework.web.context.request.RequestContextListener");
ctx.deploy(server);
server.start();
System.in.read();
}

Categories