I'm trying to upgrade as detailed above. Going from Log4J 2.11.0 to 2.17.1 completely breaks my runtime Log4J database appender setup, breaking in the underlying MariaDB JDBC driver when trying to write log entries to Percona 5.7 / MySQL 5.7.
2.11.0 is working fine and has been for a very, very long time in this app.
Trying to log to Percona 5.7 (MySQL 5.7) via the MariaDB JDBC driver.
Changed my pom.xml for Log4J2 from
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.0</version>
</dependency>
to
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
My log4j2.xml (working fine with 2.11.0 - must it be changed for 2.17.1?):
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %c{1} - %m %n" />
</Console>
<File name="MyFile" fileName="my.log" immediateFlush="true" append="true">
<PatternLayout pattern=" %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %c{1} - %m %n"/>
</File>
<JDBC name="MyDB" tableName="db.log">
<ConnectionFactory class="log4J2.utils.Log4J2GetConnection" method="getDatabaseConnection"/>
<Column name="DATED" isEventTimestamp="true" isUnicode="false"/>
<Column name="LOGGER" pattern="%logger" isUnicode="false"/>
<Column name="LEVEL" pattern="%level" isUnicode="false"/>
<Column name="MESSAGE" pattern="%message" isUnicode="false"/>
<Column name="THROWABLE" pattern="%ex{full}" isUnicode="false" />
</JDBC>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console" />
<!--<AppenderRef ref="MyFile"/>-->
<AppenderRef ref="MyDB"/>
</Root>
</Loggers>
</Configuration>
My connection factory:
package log4J2.utils;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
import AppSettings;
/**
*
* #author sv
*/
public class Log4J2GetConnection {
private static interface Singleton {
final Log4J2GetConnection INSTANCE = new Log4J2GetConnection();
}
private final DataSource dataSource;
private Log4J2GetConnection() {
if (AppSettings.getMySQLDataSource() == null) {
AppSettings.resetMySQLDataSource();
}
Properties properties = new Properties();
properties.setProperty("user", AppSettings.getMysqlUser());
properties.setProperty("password", AppSettings.getMysqlPassword());
String connectionString = "jdbc:mariadb://" + AppSettings.getMysqlServer() + ":" + AppSettings.getMysqlPort() + "/" + AppSettings.getMysqlDatabase();
GenericObjectPool pool = new GenericObjectPool();
DriverManagerConnectionFactory connectionFactory = new DriverManagerConnectionFactory(connectionString, properties);
new PoolableConnectionFactory(connectionFactory, pool, null, "SELECT 1", 3, false, false, Connection.TRANSACTION_READ_COMMITTED);
this.dataSource = new PoolingDataSource(pool);
}
public static Connection getDatabaseConnection() throws SQLException {
return Singleton.INSTANCE.dataSource.getConnection();
}
}
Recompiled fine.
Whenever I now try to make a log entry under 2.17.1, I get an exception as detailed below. Does not happen with 2.11.0, only with 2.17.1:
2022-01-18 10:16:31,243 main ERROR Unable to write to database [JdbcManager{name=MyDB, bufferSize=0, tableName=db.log, columnConfigs=[{ name=DATED, layout=null, literal=null, timestamp=true }, { name=LOGGER, layout=%logger, literal=null, timestamp=false }, { name=LEVEL, layout=%level, literal=null, timestamp=false }, { name=MESSAGE, layout=%message, literal=null, timestamp=false }, { name=THROWABLE, layout=%ex{full}, literal=null, timestamp=false }], columnMappings=[]}] for appender [MyDB]. org.apache.logging.log4j.core.appender.db.DbAppenderLoggingException: Failed to insert record for log event in JDBC manager: java.sql.SQLException: Malformed communication packet. [columnConfigs=[{ name=DATED, layout=null, literal=null, timestamp=true }, { name=LOGGER, layout=%logger, literal=null, timestamp=false }, { name=LEVEL, layout=%level, literal=null, timestamp=false }, { name=MESSAGE, layout=%message, literal=null, timestamp=false }, { name=THROWABLE, layout=%ex{full}, literal=null, timestamp=false }], sqlStatement=insert into asteriskcdrdb.application_log (DATED,LOGGER,LEVEL,MESSAGE,THROWABLE) values (?,?,?,?,?), factoryData=FactoryData [connectionSource=factory{ public static java.sql.Connection log4J2.utils.Log4J2GetConnection.getDatabaseConnection() }, tableName=asteriskcdrdb.application_log, columnConfigs=[{ name=DATED, layout=null, literal=null, timestamp=true }, { name=LOGGER, layout=%logger, literal=null, timestamp=false }, { name=LEVEL, layout=%level, literal=null, timestamp=false }, { name=MESSAGE, layout=%message, literal=null, timestamp=false }, { name=THROWABLE, layout=%ex{full}, literal=null, timestamp=false }], columnMappings=[], immediateFail=false, retry=true, reconnectIntervalMillis=5000, truncateStrings=true], connection=jdbc:mysql://172.16.1.13:3306/asteriskcdrdb, UserName=asteriskcdruser, mariadb-jdbc, statement=sql : 'insert into asteriskcdrdb.application_log (DATED,LOGGER,LEVEL,MESSAGE,THROWABLE) values (?,?,?,?,?)', parameters : ['2022-01-18 10:15:59.319','webServer.WebHost','INFO','1iemp3w272hi - WebHost class - Starting jetty...',''], reconnector=null, isBatchSupported=true, columnMetaData={MESSAGE=ColumnMetaData [schemaName=, catalogName=asteriskcdrdb, tableName=application_log, name=MESSAGE, nameKey=MESSAGE, label=MESSAGE, displaySize=65535, type=12, typeName=VARCHAR, className=java.lang.String, precision=196605, scale=0, isStringType=true], LOGGER=ColumnMetaData [schemaName=, catalogName=asteriskcdrdb, tableName=application_log, name=LOGGER, nameKey=LOGGER, label=LOGGER, displaySize=255, type=12, typeName=VARCHAR, className=java.lang.String, precision=765, scale=0, isStringType=true], DATED=ColumnMetaData [schemaName=, catalogName=asteriskcdrdb, tableName=application_log, name=DATED, nameKey=DATED, label=DATED, displaySize=19, type=93, typeName=DATETIME, className=java.sql.Timestamp, precision=19, scale=0, isStringType=false], LEVEL=ColumnMetaData [schemaName=, catalogName=asteriskcdrdb, tableName=application_log, name=LEVEL, nameKey=LEVEL, label=LEVEL, displaySize=10, type=12, typeName=VARCHAR, className=java.lang.String, precision=30, scale=0, isStringType=true], THROWABLE=ColumnMetaData [schemaName=, catalogName=asteriskcdrdb, tableName=application_log, name=THROWABLE, nameKey=THROWABLE, label=THROWABLE, displaySize=65535, type=12, typeName=VARCHAR, className=java.lang.String, precision=196605, scale=0, isStringType=true]}]
at org.apache.logging.log4j.core.appender.db.jdbc.JdbcDatabaseManager.writeInternal(JdbcDatabaseManager.java:871)
at org.apache.logging.log4j.core.appender.db.jdbc.JdbcDatabaseManager.writeThrough(JdbcDatabaseManager.java:898)
at org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager.write(AbstractDatabaseManager.java:264)
at org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender.append(AbstractDatabaseAppender.java:110)
at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:161)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:134)
at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:125)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:89)
at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:542)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:500)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:483)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:417)
at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
at org.apache.logging.log4j.core.Logger.log(Logger.java:161)
at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2205)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2159)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2142)
at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2017)
at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1983)
at org.apache.logging.log4j.spi.AbstractLogger.info(AbstractLogger.java:1320)
at webServer.WebHost.<init>(WebHost.java:58)
at App.main(App.java:255)
Caused by: java.sql.SQLException: Malformed communication packet.
at org.mariadb.jdbc.internal.SQLExceptionMapper.get(SQLExceptionMapper.java:149)
at org.mariadb.jdbc.internal.SQLExceptionMapper.throwException(SQLExceptionMapper.java:106)
at org.mariadb.jdbc.MySQLStatement.executeQueryEpilog(MySQLStatement.java:268)
at org.mariadb.jdbc.MySQLStatement.execute(MySQLStatement.java:296)
at org.mariadb.jdbc.MySQLStatement.executeUpdate(MySQLStatement.java:325)
at org.mariadb.jdbc.MySQLPreparedStatement.executeUpdate(MySQLPreparedStatement.java:159)
at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
at org.apache.logging.log4j.core.appender.db.jdbc.JdbcDatabaseManager.writeInternal(JdbcDatabaseManager.java:862)
... 21 more
Caused by: org.mariadb.jdbc.internal.common.QueryException: Malformed communication packet.
at org.mariadb.jdbc.internal.mysql.MySQLProtocol.getResult(MySQLProtocol.java:982)
at org.mariadb.jdbc.internal.mysql.MySQLProtocol.executeQuery(MySQLProtocol.java:1042)
at org.mariadb.jdbc.MySQLStatement.execute(MySQLStatement.java:289)
... 26 more
My MariaDB JDBC artifact in my pom.xml:
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>1.1.8</version>
</dependency>
E. g. all logging attempts by version 2.17.1 of Log4J fail in Percona 5.7 with the error in the MariaDB JDBC driver of
org.apache.logging.log4j.core.appender.db.DbAppenderLoggingException: Failed to insert record for log event in JDBC manager: java.sql.SQLException: Malformed communication packet.
The moment I switch back in the pom.xml to Log4J 2.11.0, the communication packets emitted by the MariaDB JDBC driver to Percona 5.7 are no longer considered "malformed".
Somehow Log4J 2.17.1 is affecting the MariaDB JDBC driver 1.1.8 in such a way that my Percona 5.7 instance rejects all SQL communication packets it receives from the JDBC driver, if the origin of said SQL insert query is Log4J 2.17.1... WTF
If the origin of the SQL query is no longer Log4J 2.17.1 but 2.11.0, the communication packages are no longer malformed according to Percona 5.7 and it happily executes the log insert query sent via the MariaDB JDBC driver from Log4J 2.11.0.
Any pointers as to how to fix this issue?
Thanks!
Stefan
FYI, Log4J vulnerability starts from 2.12.0
The solution to the above post seems to be to first update the MariaDB JDBC Java client to the current stable version. In the pom.xml for Maven:
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<!--<version>1.1.8</version>-->
<version>2.7.4</version>
</dependency>
2.7.4 of the MariaDB-java-client is stable as of 2022-01-18.
Then add commons-dbcp version 1.4 to allow my connection factory code to work correctly. In the pom.xml:
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
<type>jar</type>
</dependency>
Then finally take log4j 2.11.0 up to 2.17.1. In the pom.xml:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<!--<version>2.11.0</version>-->
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<!--<version>2.11.0</version>-->
<version>2.17.1</version>
</dependency>
After doing the above the exception of
db.DbAppenderLoggingException: Failed to insert record for log event in JDBC manager: java.sql.SQLException: Malformed communication packet.
was gone and Log4J 2.17.1 is now successfully logging via the MariaDb Java client 2.7.4 to Percona (MySQL) 5.7 with commons-dbcp 1.4 to make the above Log4J connection factory code work.
--
EDIT: here is an alternative connection factory class for using Apache DBCP2 instead of DBCP1 to get a connection for Log4J2 via MariaDB Java client 2.7.4 to Percona 5.7:
package verishare.log4J2.utils;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.pool2.ObjectPool;
import AppSettings;
/**
*
* #author sv
*/
public class Log4J2GetConnection {
private static interface Singleton {
final Log4J2GetConnection INSTANCE = new Log4J2GetConnection();
}
private final DataSource dataSource;
private Log4J2GetConnection() {
if (AppSettings.getMySQLDataSource() == null) {
AppSettings.resetMySQLDataSource();
}
String connectionString = "jdbc:mariadb://" + AppSettings.getMysqlServer() + ":" + AppSettings.getMysqlPort() + "/" + AppSettings.getMysqlDatabase();
org.apache.commons.dbcp2.PoolingDataSource<PoolableConnection> workDataSource = null;
try {
org.apache.commons.dbcp2.ConnectionFactory factory = new org.apache.commons.dbcp2.DriverManagerConnectionFactory(connectionString,
AppSettings.getMysqlUser(), AppSettings.getMysqlPassword());
org.apache.commons.dbcp2.PoolableConnectionFactory poolFactory = new org.apache.commons.dbcp2.PoolableConnectionFactory(factory, null);
ObjectPool<PoolableConnection> connectionPool = new org.apache.commons.pool2.impl.GenericObjectPool<>(poolFactory);
poolFactory.setPool(connectionPool);
workDataSource = new org.apache.commons.dbcp2.PoolingDataSource<>(connectionPool);
} catch (Exception ex) {
System.out.println(("Exception in getPoolForConnection:" + ex.toString()));
} finally {
this.dataSource = workDataSource;
}
}
public static Connection getDatabaseConnection() throws SQLException {
return Singleton.INSTANCE.dataSource.getConnection();
}
}
I am learning Java EE and for 2 days already I've been struggling with configuration of Hibernate to work with MySQL database on a TomEE server in a simple Java EE web application.
Hibernate version: 5.4.10.Final (core and entity-manager dependencies)
Java EE API: 8.0
MySQL version: 8.0.19
TomEE version: 8.0.1 (TomEE Embedded in tomee-embedded-maven-plugin)
I have 2 simple entities: Car and Seat, with the uni-directional #OneToMany relation from Car to Seat(s). Color and EngineType are plain enums, while Specification is a value object for these 2 enums). NOTE: FetchType.EAGER is used in the #OneToMany for learning purposes, I am familiar that this is not a good solution normally.
When I try to configure the persistence.xml file, alghouth specifying everything "as for MySQL", it seems that Hibernate still uses the default HSQLDB syntax/dialect/engine and as a result, I recieve errors during schema creation:
[INFO] TomEE embedded started on localhost:8080
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
Hibernate: alter table Seat drop foreign key FKkkm9pdx9e1t9jva76n9tqhhqv
mar 06, 2020 1:48:31 PM org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl handleException
WARN: GenerationTarget encountered exception accepting command : Error executing DDL "alter table Seat drop foreign key FKkkm9pdx9e1t9jva76n9tqhhqv" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "alter table Seat drop foreign key FKkkm9pdx9e1t9jva76n9tqhhqv" via JDBC Statement
(...)
Caused by: java.sql.SQLSyntaxErrorException: user lacks privilege or object not found: PUBLIC.SEAT
(...)
Caused by: org.hsqldb.HsqlException: user lacks privilege or object not found: PUBLIC.SEAT // what is going on here???
and for later Hibernate SQL commands while creating DDL, I have also:
Hibernate: create table Car (identifier bigint not null auto_increment, color varchar(255), engineType varchar(255), primary key (identifier)) engine=InnoDB
mar 06, 2020 1:48:31 PM org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl handleException
WARN: GenerationTarget encountered exception accepting command : Error executing DDL "create table Car (identifier bigint not null auto_increment, color varchar(255), engineType varchar(255), primary key (identifier)) engine=InnoDB" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table Car (identifier bigint not null auto_increment, color varchar(255), engineType varchar(255), primary key (identifier)) engine=InnoDB" via JDBC Statement
(...)
Caused by: java.sql.SQLSyntaxErrorException: unexpected token: AUTO_INCREMENT
(...)
Caused by: org.hsqldb.HsqlException: unexpected token: AUTO_INCREMENT // definitely something messed up, this is a correct MySQL token
When I omit all "MySQL stuff" (driver and dialect), and make Hibernate use default HSQLDB, it works fine... The DDL for HSQLDB is created well.
I tried many different configurations and replacements googling it over the web and SO, but not found any hint why Hibernate still uses non-MySQL syntax (but I am not 100% sure that this is the direct cause of the issue).
I attempted to specify the datasource in resources.xml file under webapp, but it doesn't change anything. Additionally, I checked if HSQLDB could be excluded from Maven build, but it ships with hibernate-core, so it's not easily achievable and perhaps could also not help.
I have previously used Hibernate with Spring and SpringBoot, but with Java EE I am totally lost here, that's why I asked the question.
Could anyone help with the issue and give some advices on the correct configuration of all this?
My persistence.xml under resources/META-INF looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="my-persistence-unit" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>java:openejb/Resource/myJtaDatabase</jta-data-source>
<!-- Entity classes -->
<class>com.example.javaeecourse.entity.Car</class>
<class>com.example.javaeecourse.entity.Seat</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/javaeecourse?serverTimezone=UTC" />
<property name="hibernate.connection.username" value="root" />
<property name="hibernate.connection.password" value="admin" />
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="create"/>
<property name="tomee.jpa.factory.lazy" value="true" />
<!--<property name="tomee.jpa.cdi=false" value="false" />-->
</properties>
</persistence-unit>
</persistence>
Car entity:
package com.example.javaeecourse.entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
import static com.example.javaeecourse.entity.Car.FIND_ALL;
#Getter
#Setter
#Entity
#Table(name = "cars")
#NamedQuery(name = FIND_ALL, query = "SELECT car FROM Car car")
#NoArgsConstructor
public class Car {
public static final String FIND_ALL = "Car.findAll";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long identifier;
#Enumerated(EnumType.STRING)
private Color color;
#Enumerated(EnumType.STRING)
private EngineType engineType;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "car", nullable = false)
private Set<Seat> seats = new HashSet<>();
}
Seat entity:
package com.example.javaeecourse.entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
#Entity
#Table(name = "seats")
#NoArgsConstructor
#Getter
#Setter
public class Seat {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String seatMaterial;
public Seat(String seatMaterial) {
this.seatMaterial = seatMaterial;
}
}
CarManufacturer class (#Stateless EJB, where EntityManager is called):
package com.example.javaeecourse.boundary;
import com.example.javaeecourse.control.CarFactory;
import com.example.javaeecourse.entity.Car;
import com.example.javaeecourse.entity.CarCreatedEvent;
import com.example.javaeecourse.entity.Specification;
import javax.ejb.Stateless;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
#Stateless
public class CarManufacturer {
#Inject
CarFactory carFactory;
#Inject
Event<CarCreatedEvent> carCreatedEvent;
#PersistenceContext
EntityManager entityManager;
public Car manufactureCar(Specification specification) {
Car car = carFactory.createCar(specification);
entityManager.persist(car);
carCreatedEvent.fire(new CarCreatedEvent(car.getIdentifier()));
return car;
}
public List<Car> retrieveCars() {
return entityManager.createNamedQuery(Car.FIND_ALL, Car.class).getResultList();
}
}
CarFactory class, which instantiates the entity:
package com.example.javaeecourse.control;
import com.example.javaeecourse.entity.*;
import javax.inject.Inject;
public class CarFactory {
#Inject
#DefaultCarColor
Color randomCarColor;
#Inject
#DefaultCarEngineType
EngineType randomCarEngineType;
public Car createCar(Specification specification) {
Car car = new Car();
car.setColor(specification.getColor() == null ? randomCarColor : specification.getColor());
car.setEngineType(specification.getEngineType() == null ? randomCarEngineType : specification.getEngineType());
Seat seat = new Seat("Leather");
car.getSeats().add(seat);
return car;
}
}
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>javaeecourse</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Java EE Course App</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.10.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<finalName>javaeecourse</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<groupId>org.apache.tomee.maven</groupId>
<artifactId>tomee-embedded-maven-plugin</artifactId>
<version>8.0.1</version>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
I will be grateful for any help.
Thanks to the hint provided by #areus in the question comment, I was able to resolve the case. I am posting an answer here if anyone had a similar issue in the future.
There was 1 major issue and 1 "bad practice" issue.
The main problem was that I was using a JTA transaction type and referenced the jta-data-source by JNDI name, but the resources.xml was located under webapp instead of resources/META-INF.
After I moved the resource.xml to the META-INF, the database could be properly created and entities were persisted.
Additionally, in my configuration, I have used a deprecated JDBC Driver for MySQL 8. According to this doc, since MySQL 8.0, the driver is now com.mysql.cj.jdbc.Driver, not com.mysql.jdbc.Driver.
I also removed the unnecessary properties, so the config is now quite clear and everything works fine.
resources/META-INF/persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="my-persistence-unit" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>jdbc/javaeecourse</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
<property name="tomee.jpa.factory.lazy" value="true"/>
</properties>
</persistence-unit>
</persistence>
resources/META-INF/resources.xml
<?xml version="1.0" encoding="UTF-8"?>
<tomee>
<Resource id="jdbc/javaeecourse" type="javax.sql.DataSource">
JdbcDriver = com.mysql.cj.jdbc.Driver
JdbcUrl = jdbc:mysql://localhost:3306/javaeecourse?serverTimezone=UTC
UserName = root
Password = admin
jtaManaged = true
</Resource>
</tomee>
Here is also a great article regarding the JTA/RESOURCE_LOCAL transaction types as well as jta-data-source and non-jta-data-source configuration, which helped me understand the details.
I have some JUnit tests on code that uses a kafka topic. The mock kafka topics I've tried do not work and the examples found online are very old so they also do not work with 0.8.2.1. How do I create a mock kafka topic using 0.8.2.1?
To clarify: I'm choosing to use an actual embedded instance of the topic in order to test with a real instance rather than mocking the hand off in mockito. This is so I can test that my custom encoders and decoders actually work and it doesn't fail when I go to use a real kafka instance.
https://gist.github.com/asmaier/6465468#file-kafkaproducertest-java
This example was updated to be working in the new 0.8.2.2 version. Here is the code snippet with maven dependencies:
pom.xml:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>0.8.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>0.8.2.2</version>
<classifier>test</classifier>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.8.2.2</version>
</dependency>
</dependencies>
KafkaProducerTest.java:
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.I0Itec.zkclient.ZkClient;
import org.junit.Test;
import kafka.admin.TopicCommand;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
import kafka.producer.KeyedMessage;
import kafka.producer.Producer;
import kafka.producer.ProducerConfig;
import kafka.server.KafkaConfig;
import kafka.server.KafkaServer;
import kafka.utils.MockTime;
import kafka.utils.TestUtils;
import kafka.utils.TestZKUtils;
import kafka.utils.Time;
import kafka.utils.ZKStringSerializer$;
import kafka.zk.EmbeddedZookeeper;
import static org.junit.Assert.*;
/**
* For online documentation
* see
* https://github.com/apache/kafka/blob/0.8.2/core/src/test/scala/unit/kafka/utils/TestUtils.scala
* https://github.com/apache/kafka/blob/0.8.2/core/src/main/scala/kafka/admin/TopicCommand.scala
* https://github.com/apache/kafka/blob/0.8.2/core/src/test/scala/unit/kafka/admin/TopicCommandTest.scala
*/
public class KafkaProducerTest {
private int brokerId = 0;
private String topic = "test";
#Test
public void producerTest() throws InterruptedException {
// setup Zookeeper
String zkConnect = TestZKUtils.zookeeperConnect();
EmbeddedZookeeper zkServer = new EmbeddedZookeeper(zkConnect);
ZkClient zkClient = new ZkClient(zkServer.connectString(), 30000, 30000, ZKStringSerializer$.MODULE$);
// setup Broker
int port = TestUtils.choosePort();
Properties props = TestUtils.createBrokerConfig(brokerId, port, true);
KafkaConfig config = new KafkaConfig(props);
Time mock = new MockTime();
KafkaServer kafkaServer = TestUtils.createServer(config, mock);
String [] arguments = new String[]{"--topic", topic, "--partitions", "1","--replication-factor", "1"};
// create topic
TopicCommand.createTopic(zkClient, new TopicCommand.TopicCommandOptions(arguments));
List<KafkaServer> servers = new ArrayList<KafkaServer>();
servers.add(kafkaServer);
TestUtils.waitUntilMetadataIsPropagated(scala.collection.JavaConversions.asScalaBuffer(servers), topic, 0, 5000);
// setup producer
Properties properties = TestUtils.getProducerConfig("localhost:" + port);
ProducerConfig producerConfig = new ProducerConfig(properties);
Producer producer = new Producer(producerConfig);
// setup simple consumer
Properties consumerProperties = TestUtils.createConsumerProperties(zkServer.connectString(), "group0", "consumer0", -1);
ConsumerConnector consumer = kafka.consumer.Consumer.createJavaConsumerConnector(new ConsumerConfig(consumerProperties));
// send message
KeyedMessage<Integer, byte[]> data = new KeyedMessage(topic, "test-message".getBytes(StandardCharsets.UTF_8));
List<KeyedMessage> messages = new ArrayList<KeyedMessage>();
messages.add(data);
producer.send(scala.collection.JavaConversions.asScalaBuffer(messages));
producer.close();
// deleting zookeeper information to make sure the consumer starts from the beginning
// see https://stackoverflow.com/questions/14935755/how-to-get-data-from-old-offset-point-in-kafka
zkClient.delete("/consumers/group0");
// starting consumer
Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
topicCountMap.put(topic, 1);
Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
KafkaStream<byte[], byte[]> stream = consumerMap.get(topic).get(0);
ConsumerIterator<byte[], byte[]> iterator = stream.iterator();
if(iterator.hasNext()) {
String msg = new String(iterator.next().message(), StandardCharsets.UTF_8);
System.out.println(msg);
assertEquals("test-message", msg);
} else {
fail();
}
// cleanup
consumer.shutdown();
kafkaServer.shutdown();
zkClient.close();
zkServer.shutdown();
}
}
Be sure to check your mvn dependency:tree for any conflicting libraries. I had to add exclusions for slf and log4j:
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>0.8.2.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>0.8.2.2</version>
<classifier>test</classifier>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.8.2.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
Another option I'm looking into is using apache curator:
Is it possible to start a zookeeper server instance in process, say for unit tests?
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<version>2.2.0-incubating</version>
<scope>test</scope>
</dependency>
TestingServer zkTestServer;
#Before
public void startZookeeper() throws Exception {
zkTestServer = new TestingServer(2181);
cli = CuratorFrameworkFactory.newClient(zkTestServer.getConnectString(), new RetryOneTime(2000));
}
#After
public void stopZookeeper() throws IOException {
cli.close();
zkTestServer.stop();
}
Have you tried mocking kafka consumer objects using a mocking framework like Mockito?
Am using Jersey 1.15, JDK 1.6, Tomcat 7, Maven 2.2.1 to create a simple Restful Web Service that should return a JSON String from a POJO that I mapped.
Here's my pojo:
package com.myservice.domain;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Person {
private String firstName;
private String lastName;
// Getters & Setters
}
my webservice:
package com.myservice.resource;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.myservice.domain.Person;
#Path("/service")
public class MyService {
#GET
#Path("showPerson")
#Produces(MediaType.APPLICATION_JSON)
public Person getPerson() {
Person person = new Person();
person.setFirstName("John");
person.setLastName("Doe");
return person;
}
}
Here's my pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.myservice</groupId>
<artifactId>myservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>My Web Service</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-servlet</artifactId>
<version>1.15</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.9</version>
</dependency>
</dependencies>
<build>
<finalName>myservice</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
My web.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"
metadata-complete="true">
<display-name>My Web Service</display-name>
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.myservice.resource</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
After deploying to tomcat, when I try to invoke the web service like this:
curl -X GET http://localhost:8080/myservice/rest/service/showPerson
I get the following error:
Nov 20, 2012 10:40:06 AM com.sun.jersey.spi.container.ContainerResponse write
SEVERE: A message body writer for Java class com.myservice.domain.Person,
and Java type class com.myservice.domain.Person,
and MIME media type application/json was not found
Nov 20, 2012 10:40:06 AM com.sun.jersey.spi.container.ContainerResponse write
SEVERE: A message body writer for Java class com.myservice.domain.Person,
and Java type class com.myservice.domain.Person, and MIME media type
application/json was not found
Nov 20, 2012 10:40:06 AM com.sun.jersey.spi.container.ContainerResponse write
SEVERE: The registered message body writers compatible with the
MIME media type are: */* ->
com.sun.jersey.core.impl.provider.entity.FormProvider
com.sun.jersey.core.impl.provider.entity.StringProvider
com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
com.sun.jersey.core.impl.provider.entity.FileProvider
com.sun.jersey.core.impl.provider.entity.InputStreamProvider
com.sun.jersey.core.impl.provider.entity.DataSourceProvider
com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General
com.sun.jersey.core.impl.provider.entity.ReaderProvider
com.sun.jersey.core.impl.provider.entity.DocumentProvider
com.sun.jersey.core.impl.provider.entity.StreamingOutputProviderfg
com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter
com.sun.jersey.server.impl.template.ViewableMessageBodyWriter
com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General
com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General
Nov 20, 2012 10:40:06 AM com.sun.jersey.spi.container.ContainerResponse
write
SEVERE: The registered message body writers compatible with the
MIME media type are:
*/* ->
com.sun.jersey.core.impl.provider.entity.FormProvider
com.sun.jersey.core.impl.provider.entity.StringProvider
com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
com.sun.jersey.core.impl.provider.entity.FileProvider
com.sun.jersey.core.impl.provider.entity.InputStreamProvider
com.sun.jersey.core.impl.provider.entity.DataSourceProvider
com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General
com.sun.jersey.core.impl.provider.entity.ReaderProvider
com.sun.jersey.core.impl.provider.entity.DocumentProvider
com.sun.jersey.core.impl.provider.entity.StreamingOutputProvider
com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter
com.sun.jersey.server.impl.template.ViewableMessageBodyWriter
com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General
com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General
SEVERE: Mapped exception to response: 500 (Internal Server Error)
javax.ws.rs.WebApplicationException:
com.sun.jersey.api.MessageException:
A message body writer for Java class
com.myservice.domain.Person,
and Java type class com.myservice.domain.Person,
and MIME media type application/json was not found
com.sun.jersey.api.MessageException:
A message body writer for Java class com.myservice.domain.Person,
and Java type class com.myservice.domain.Person, and MIME media type
application/json was not found
HTTP Status 500 - Internal Server Error
The server encountered an internal error that prevented it from
fulfilling this request.
After scouring the Internet, I tried inserting the following proposed solution that everyone suggested into web.xml:
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
But it still gives me the same issue! It does return the correct data marshalled to XML if I use APPLICATION_XML, however.
Would really appreciate it if someone could point me in the right direction.
Thank you very much for taking the time to read this.
Add Genson library in your dependencies.
It will automatically enable JSON-POJO databinding.
Add jersey-json to your dependencies. It will add a JSON serialization implementation.
Add this to pom.xml
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.8</version>
</dependency>