I have a java application running on a bluemix cloud server, I originally developed it locally on a tomcat server and then decided to migrate to the cloud. The option suggested everywhere was to use liberty and sqldb services which after some finicking I got setup on my bluemix account with the sql database named SQL-RCT bound as a service to my java application.
The problem is encountered when running the following code:
#WebServlet({ "/LoginServlet", "/" })
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private Connection conn;
#Resource(lookup="jdbc/SQL-RCT")
private DataSource myDataSource;
/**
* #see HttpServlet#HttpServlet()
*/
public LoginServlet() {
super();
try {
if(myDataSource == null){
throw new Exception("no data source");
}
conn = myDataSource.getConnection();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
when I try to load the servlet I get an error that there was a nullpointer exception in my init function which I quickly was able to narrow down to my myDataSource object being null.
I've checked the server.xml and I´m using the right name for the lookup but the lookup doesn't seem to work, any help would be appreciated.
the server.xml
<server>
<featureManager>
<feature>beanValidation-1.1</feature>
<feature>cdi-1.2</feature>
<feature>ejbLite-3.2</feature>
<feature>el-3.0</feature>
<feature>jaxrs-2.0</feature>
<feature>jdbc-4.1</feature>
<feature>jndi-1.0</feature>
<feature>jpa-2.1</feature>
<feature>jsf-2.2</feature>
<feature>jsonp-1.0</feature>
<feature>jsp-2.3</feature>
<feature>managedBeans-1.0</feature>
<feature>servlet-3.1</feature>
<feature>websocket-1.1</feature>
<feature>icap:managementConnector-1.0</feature>
<feature>appstate-1.0</feature>
<feature>cloudAutowiring-1.0</feature>
</featureManager>
<application name='myapp' location='myapp.war' type='war' context-root='/'/>
<cdi12 enableImplicitBeanArchives='false'/>
<httpEndpoint id='defaultHttpEndpoint' host='*' httpPort='${port}'/>
<webContainer trustHostHeaderPort='true' extractHostHeaderPort='true'/>
<include location='runtime-vars.xml'/>
<logging logDirectory='${application.log.dir}' consoleLogLevel='INFO'/>
<httpDispatcher enableWelcomePage='false'/>
<applicationMonitor dropinsEnabled='false' updateTrigger='mbean'/>
<config updateTrigger='mbean'/>
<appstate appName='myapp' markerPath='${home}/../.liberty.state'/>
<dataSource id='db2-SQL-RCT' jdbcDriverRef='db2-driver' jndiName='jdbc/SQL-RCT' statementCacheSize='30' transactional='true'>
<properties.db2.jcc id='db2-SQL-RCT-props' databaseName='${cloud.services.SQL-RCT.connection.db}' user='${cloud.services.SQL-RCT.connection.username}' password='${cloud.services.SQL-RCT.connection.password}' portNumber='${cloud.services.SQL-RCT.connection.port}' serverName='${cloud.services.SQL-RCT.connection.host}'/>
</dataSource>
<jdbcDriver id='db2-driver' libraryRef='db2-library'/>
<library id='db2-library'>
<fileset id='db2-fileset' dir='${server.config.dir}/lib' includes='db2jcc4.jar db2jcc_license_cu.jar'/>
</library>
</server>
Injected resources are not available within servlet constructors, since the resources do not get injected until after the servlet instance has been fully initialized.
Instead, override the javax.servlet.GenericServlet init() method and get your conneciton there. This lifecycle method will give you similar lifecycle behavior as how you are currently trying to create your connection in the servlet constructor.
Example code:
#WebServlet({ "/LoginServlet", "/" })
public class LoginServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
private Connection conn;
#Resource(lookup="jdbc/SQL-RCT")
private DataSource myDataSource;
#Override
public void init() throws ServletException {
super.init();
try {
conn = myDataSource.getConnection();
} catch (Exception e) {
throw new ServletException(e);
}
}
}
As a side note:
Since Liberty pools connections, it's not necessary to store a connection at the class scope. If you get connections when they are needed and close them once you are done using them, you should not see any performance difference.
If you want to get a connection in the servlet init code as a way to eagerly get a connection, that is fine, but it will impact your servlet load time.
In most containers, the naming convention for the #Resource annotation is as follows:
#Resource(name = "java:/comp/env/jdbc/SQL-RCT")
private DataSource myDataSource;
Found it on this answer:
JNDI #Resource annotation
Related
Tis is my first time deploying with weblogic, I have a spring boot application and when i add in weblogic than I can't find the path for my rest call, in my main application I even added this log to see where is running but i get no output:
private static final Logger LOGGER = LoggerFactory.getLogger(KpiApplication.class);
public static void main(String[] args) throws UnknownHostException {
SpringApplication app = new SpringApplication(KpiApplication.class);
Environment env = app.run(args).getEnvironment();
String protocol = "http";
if (env.getProperty("server.ssl.key-store") != null) {
protocol = "https";
}
System.out.println("LEXA"+ env.getProperty("server.port")+InetAddress.getLocalHost().getHostAddress());
try {
LOGGER.info("\n----------------------------------------------------------\n\t" +
"Application '{}' is running! Access URLs:\n\t" +
"Local: \t\t{}://localhost:{}\n\t" +
"External: \t{}://{}:{}\n\t" +
"Profile(s): \t{}\n----------------------------------------------------------",
env.getProperty("spring.application.name"),
protocol,
env.getProperty("server.port"),
protocol,
InetAddress.getLocalHost().getHostAddress(),
env.getProperty("server.port"),
env.getActiveProfiles());
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SpringApplication.run(KpiApplication.class);
}
}
In my weblogic i see this path in this section but i just says page not found:
The rest I'm trying to execute is this but everytime is a page not found:
#RestController
public class AccountController extends KpiAbstractController {
#Autowired
private AccountService accountService;
#GetMapping("/v1/accounts")
public ResponseEntity<AccountDTO> getAccounts(#RequestParam #ApiParam("Point of sale owner ID") String ownerPosId,
#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startPeriod,
#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endPeriod,
HttpServletRequest request)
All the application is already setted up and my manager just told me try it, but he doesn't know the link too, any idea where I can find it please
Have you done what's written in Spring Boot docs "traditional deployment" part? https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.traditional-deployment
Looks like, you didn't.
You get no output from your logs, because public static void main will not be called on WebLogic - you will need to extend SpringBootServletInitializer instead.
Also for weblogic you would usually create src/main/webapp/weblogic.xml file where you would set context-root, e.g.
<?xml version="1.0" encoding="UTF-8"?>
<weblogic-web-app xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.5/weblogic-web-app.xsd">
<context-root>myapp</context-root>
</weblogic-web-app>
Which make you application accessible at http://server:port/myapp URL.
Another thing you didn't mention is if you have a sole AdminServer or AdminServer+ManagedServer? If the letter, then you should deploy your application via AdminServer to ManagedServer and access it at ManagedServer host:port.
In Java8 there is a static block in java.sql.DriverManger class as
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
It will be executed when java.sql.DriverManger class is loaded by ClassLoader, and it will call the ServiceLoader.load() method to start to scan files under META-IFO/services folder in jars under the classpath. In this way it register all the Driver class defined in services folder.
However, in Java11, it don't have this static block anymore, I was wondering how Java11 starts the SPI process. Thanks for any answers.
In Java 11 the scanning for the drivers is only started when the first connection is opened:
DriverManager.getConnection(String url)
public static Connection getConnection(String url)
throws SQLException {
java.util.Properties info = new java.util.Properties();
return (getConnection(url, info, Reflection.getCallerClass()));
}
calls DriverManager.getConnection(String url, Properties info, Class<?> caller):
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
// [..]
ensureDriversInitialized();
// [..]
}
which in turn calls DriverManager.ensureDriversInitialized() which finally uses the java.util.ServiceLoader class to effectively load the drivers:
private static void ensureDriversInitialized() {
// [..]
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// [..]
}
I have a the following websocket endpoint:
import javax.inject.Inject;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
#ServerEndpoint(value = "/blabla")
public class WebsocketService {
#Inject
private DatabaseProvider dbProvider;
#OnOpen
public void onOpen(Session session) throws IOException {
//do something
}
#OnMessage
public void onMessage(Session session, String socketPacket) throws IOException {
//do something else
}
...
}
The code to start the embedded server:
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import javax.websocket.server.ServerContainer;
//other imports
public static void main(String[] args) {
Server server = null;
try {
server = new Server(3081);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
ServerContainer serverContainer = WebSocketServerContainerInitializer.configureContext(context);
serverContainer.addEndpoint(WebsocketService.class);
server.setHandler(context);
server.start();
server.join();
} catch (Exception e) {
logger.error(e.getMessage());
} finally {
if (server != null) {
server.destroy();
}
}
}
The code above works perfectly for the case without dependency injection. However, I want to inject the dbProvider into my WebsocketService and use it in the onMessage method.
QUESTION 1: How to do the injection for the websocket server?
P.S. There are multiple examples of how dependency injection is done for REST endpoinds using ResourceConfig + AbstractBinder + ServletContainer, but I am not sure how it can be applied for the case with the websocket server.
QUESTION 2: How to add a simple resource endpoint to the same server (to serve javascript)?
Quite a few moving parts in this question.
First you have to setup Weld (the CDI implementation) to properly integrate it with your ServletContextHandler
Typically seen like this ...
ServletContextHandler context = new ServletContextHandler();
// Enable Weld + CDI
context.setInitParameter(
CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE,
CdiDecoratingListener.MODE);
context.addBean(
new ServletContextHandler.Initializer(context,
new CdiServletContainerInitializer()));
context.addBean(
new ServletContextHandler.Initializer(context,
new org.jboss.weld.environment.servlet.EnhancedListener()));
Then the injection (actually decoration) is automatically taken care of internally between Jetty and Weld.
Note: the ServletContexthandler.Initializer is a convenience class to allow your embedded-jetty to run an arbitrary javax.servlet.ServletContainerInitializer without all of the overhead of a full blown WebApp and it's complex initialization process.
The CdiServletContainerInitializer is a ServletContainerInitializer that Jetty provides which sets up various things in the ServletContext to allow Weld to wire itself up properly to the ServletContext.
The EnhancedListener is also a ServletContainerInitializer that weld provides which does it's side of the wiring up for Weld + CDI.
For serving static files, you'll want to have a "Base Resource" defined in your ServletContextHandler and then add the DefaultServlet to the "default" url-pattern of "/".
ServletContextHandler context = new ServletContextHandler();
context.setBaseResource(Resource.newResource(webRootUri));
context.addServlet(DefaultServlet.class, "/");
If you want to see all of this together, check out the example project at
https://github.com/jetty-project/embedded-jetty-weld
I am not able to figure out how to implement this. Any help and/or pointers will be greatly appreciated.
Currently, my Java/Spring application backend is deployed on EC2 and accessing MySQL on RDS successfully using the regular Spring JDBC setup. That is, storing database info in application.properties and configuring DataSource and JdbcTemplate in #Configuration class. Everything works fine.
Now, I need to access MySQL on RDS securely. RDS instance has IAM Authentication enabled. I have also successfully created IAM role and applied inline policy. Then, following the AWS RDS documentation and Java example on this link, I am able to access the database from a standalone Java class successfully using Authentication Token and the user I created instead of regular db username and password. This standalone Java class is dealing with "Connection" object directly.
The place I am stuck is how I translate this to Spring JDBC configuration. That is, setting up DataSource and JdbcTemplate beans for this in my #Configuration class.
What would be a correct/right approach to implement this?
----- EDIT - Start -----
I am trying to implement this as a library that can be used for multiple projects. That is, it will be used as a JAR and declared as a dependency in a project's POM file. This library is going to include configurable AWS Services like this RDS access using general DB username and password, RDS access using IAM Authentication, KMS (CMK/data keys) for data encryption, etc.
Idea is to use this library on any web/app server depending on the project.
Hope this clarifies my need more.
----- EDIT - End -----
DataSource internally has getConnection() so I can basically create my own DataSource implementation to achieve what I want. But is this a good approach?
Something like:
public class MyDataSource implements DataSource {
#Override
public Connection getConnection() throws SQLException {
Connection conn = null;
// get a connection using IAM Authentication Token for accessing AWS RDS, etc. as in the AWS docs
return conn;
}
#Override
public Connection getConnection(String username, String password) throws SQLException {
return getConnection();
}
//other methods
}
You can use the following snippet as a replacement for the default connection-pool provided by SpringBoot/Tomcat. It will refresh the token password every 10 minutes, since the token is valid for 15 minutes. Also, it assumes the region can be extracted from the DNS hostname. If this is not the case, you'll need to specify the region to use.
public class RdsIamAuthDataSource extends org.apache.tomcat.jdbc.pool.DataSource {
private static final Logger LOG = LoggerFactory.getLogger(RdsIamAuthDataSource.class);
/**
* The Java KeyStore (JKS) file that contains the Amazon root CAs
*/
public static final String RDS_CACERTS = "/rds-cacerts";
/**
* Password for the ca-certs file.
*/
public static final String PASSWORD = "changeit";
public static final int DEFAULT_PORT = 3306;
#Override
public ConnectionPool createPool() throws SQLException {
return pool != null ? pool : createPoolImpl();
}
protected synchronized ConnectionPool createPoolImpl() throws SQLException {
return pool = new RdsIamAuthConnectionPool(poolProperties);
}
public static class RdsIamAuthConnectionPool extends ConnectionPool implements Runnable {
private RdsIamAuthTokenGenerator rdsIamAuthTokenGenerator;
private String host;
private String region;
private int port;
private String username;
private Thread tokenThread;
public RdsIamAuthConnectionPool(PoolConfiguration prop) throws SQLException {
super(prop);
}
#Override
protected void init(PoolConfiguration prop) throws SQLException {
try {
URI uri = new URI(prop.getUrl().substring(5));
this.host = uri.getHost();
this.port = uri.getPort();
if (this.port < 0) {
this.port = DEFAULT_PORT;
}
this.region = StringUtils.split(this.host,'.')[2]; // extract region from rds hostname
this.username = prop.getUsername();
this.rdsIamAuthTokenGenerator = RdsIamAuthTokenGenerator.builder().credentials(new DefaultAWSCredentialsProviderChain()).region(this.region).build();
updatePassword(prop);
final Properties props = prop.getDbProperties();
props.setProperty("useSSL","true");
props.setProperty("requireSSL","true");
props.setProperty("trustCertificateKeyStoreUrl",getClass().getResource(RDS_CACERTS).toString());
props.setProperty("trustCertificateKeyStorePassword", PASSWORD);
super.init(prop);
this.tokenThread = new Thread(this, "RdsIamAuthDataSourceTokenThread");
this.tokenThread.setDaemon(true);
this.tokenThread.start();
} catch (URISyntaxException e) {
throw new RuntimeException(e.getMessage());
}
}
#Override
public void run() {
try {
while (this.tokenThread != null) {
Thread.sleep(10 * 60 * 1000); // wait for 10 minutes, then recreate the token
updatePassword(getPoolProperties());
}
} catch (InterruptedException e) {
LOG.debug("Background token thread interrupted");
}
}
#Override
protected void close(boolean force) {
super.close(force);
Thread t = tokenThread;
tokenThread = null;
if (t != null) {
t.interrupt();
}
}
private void updatePassword(PoolConfiguration props) {
String token = rdsIamAuthTokenGenerator.getAuthToken(GetIamAuthTokenRequest.builder().hostname(host).port(port).userName(this.username).build());
LOG.debug("Updated IAM token for connection pool");
props.setPassword(token);
}
}
}
Please note that you'll need to import Amazon's root/intermediate certificates to establish a trusted connection. The example code above assumes that the certificates have been imported into a file called 'rds-cacert' and is available on the classpath. Alternatively, you can also import them into the JVM 'cacerts' file.
To use this data-source, you can use the following properties for Spring:
datasource:
url: jdbc:mysql://dbhost.xyz123abc.us-east-1.rds.amazonaws.com/dbname
username: iam_app_user
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.mydomain.jdbc.RdsIamAuthDataSource
Using Spring Java config:
#Bean public DataSource dataSource() {
PoolConfiguration props = new PoolProperties();
props.setUrl("jdbc:mysql://dbname.abc123xyz.us-east-1.rds.amazonaws.com/dbschema");
props.setUsername("iam_dbuser_app");
props.setDriverClassName("com.mysql.jdbc.Driver");
return new RdsIamAuthDataSource(props);
}
UPDATE: When using MySQL, you can also decide to use the MariaDB JDBC driver, which has builtin support for IAM authentication:
spring:
datasource:
host: dbhost.cluster-xxx.eu-west-1.rds.amazonaws.com
url: jdbc:mariadb:aurora//${spring.datasource.host}/db?user=xxx&credentialType=AWS-IAM&useSsl&serverSslCert=classpath:rds-combined-ca-bundle.pem
type: org.mariadb.jdbc.MariaDbPoolDataSource
The above requires MariaDB and AWS SDK libraries, and needs the CA-bundle in the classpath
I know this is an older question, but after a some searching I found a pretty easy way you can now do this using the MariaDB driver. In version 2.5 they added an AWS IAM credential plugin to the driver. It will handle generating, caching and refreshing the token automatically.
I've tested using Spring Boot 2.3 with the default HikariCP connection pool and it is working fine for me with these settings:
spring.datasource.url=jdbc:mariadb://host/db?credentialType=AWS-IAM&useSsl&serverSslCert=classpath:rds-combined-ca-bundle.pem
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.username=iam_username
#spring.datasource.password=dont-need-this
spring.datasource.hikari.maxLifetime=600000
Download rds-combined-ca-bundle.pem and put it in src/main/resources so you can connect via SSL.
You will need these dependencies on the classpath as well:
runtime 'org.mariadb.jdbc:mariadb-java-client'
runtime 'com.amazonaws:aws-java-sdk-rds:1.11.880'
The driver uses the standard DefaultAWSCredentialsProviderChain so make sure you have credentials with policy allowing IAM DB access available wherever you are running your app.
Hope this helps someone else - most examples I found online involved custom code, background threads, etc - but using the new driver feature is much easier!
There is a library that can make this easy. Effectively you just override the getPassword() method in the HikariDataSource. You use STS to assume the role and send a "password" for that role.
<dependency>
<groupId>io.volcanolabs</groupId>
<artifactId>rds-iam-hikari-datasource</artifactId>
<version>1.0.4</version>
</dependency>
For school they're making us connect to a postgresDB trough plain old dao's and tomcat. However the given code ain't working and I've been stuck here for quite a bit now.
So here goes.
The connectiondao given:
package nl.hu.v1wac.firstapp.persistence;
import java.sql.Connection;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class BaseDao {
protected final Connection getConnection() {
Connection result = null;
try {
InitialContext ic = new InitialContext();
DataSource ds = (DataSource)ic.lookup("java:comp/env/jdbc/PostgresDS");
result = ds.getConnection();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return result;
}
}
We had to write the server specifics into an context.xml file, and import the jar driver into the lib folder of tomcat (so far so good). The context.xml is in the src/main/webapp/META-INF directory
The context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/PostgresDS"
url="jdbc:postgresql://localhost:5432/worlddb"
driverClassName="org.postgresql.Driver"
auth="Container"
type="javax.sql.DataSource"
username="postgres"
password="secret" />
</Context>
After setting up my Dao's, I try to fire them up in a main and get the following error:
Exception in thread "main" java.lang.RuntimeException: javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
which is said caused by:
DataSource ds = (DataSource)ic.lookup("java:comp/env/jdbc/PostgresDS");
Would anyone be able to help, as according the slides / manual this should be all to it :/
Thanks in advance!
p.s. we're using tomcat 8.5 and Eclipse Jee Neon
Edited Main class
package nl.hu.v1wac.firstapp.persistence;
import java.sql.SQLException;
public class Pattern {
public static void main(String[] args) {
// TODO Auto-generated method stub
CountryDao cdao = new CountryDaoPostgreSQL();
try {
cdao.findALl();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
You certainly shouldn't be getting that.
You're running this code within Tomcat proper? Not outside? The complaint is that the InitialContext doesn't have the underlying "plumbing" to do the lookup. Normally this is all managed by the environment that you're running in, unless you're running it "stand alone" in a Java SE app, you should never see this.