Trying to migrate an application from WebLogic 12.2.1 to Tomcat 8.5.4, what under Weblogic was an entry as Foreign JNDI Providers for an LDAP connection has been migrated to a new Resource under Tomcat.
Following this advice on Stack Overflow, a custom LdapContextFactory has been packaged as a new jar file under Tomcat lib folder.
In the Tomcat server.xml file the following GlobalNamingResources/Resource has been configured:
<Resource name="ldapConnection"
auth="Container"
type="javax.naming.ldap.LdapContext"
factory="com.sample.custom.LdapContextFactory"
singleton="false"
java.naming.referral="follow"
java.naming.factory.initial="com.sun.jndi.ldap.LdapCtxFactory"
java.naming.provider.url="ldap://some.host:389"
java.naming.security.authentication="simple"
java.naming.security.principal="CN=some,OU=some,OU=some,DC=some,DC=a,DC=b"
java.naming.security.credentials="password"
com.sun.jndi.ldap.connect.pool="true"
com.sun.jndi.ldap.connect.pool.maxsize="10"
com.sun.jndi.ldap.connect.pool.prefsize="4"
com.sun.jndi.ldap.connect.pool.timeout="30000" />
The connection above works fine when browsing the LDAP directory via an LDAP browser like Apache Directory Studio / LDAP Browser embedded in Eclipse.
The custom com.sample.custom.LdapContextFactory is quite simple:
public class LdapContextFactory implements ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
Hashtable<Object, Object> env = new Hashtable<>();
Reference reference = (Reference) obj;
Enumeration<RefAddr> references = reference.getAll();
while (references.hasMoreElements()) {
RefAddr address = references.nextElement();
String type = address.getType();
String content = (String) address.getContent();
env.put(type, content);
}
return new InitialLdapContext(env, null);
}
}
However, at start-up Tomcat is throwing the following exception:
07-Sep-2016 15:04:01.064 SEVERE [main] org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans Exception processing Global JNDI Resources
javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-031001E5, problem 2001 (NO_OBJECT), data 0, best match of:
''
]; remaining name ''
at com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3160)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:3081)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2888)
at com.sun.jndi.ldap.LdapCtx.c_listBindings(LdapCtx.java:1189)
at com.sun.jndi.toolkit.ctx.ComponentContext.p_listBindings(ComponentContext.java:592)
at com.sun.jndi.toolkit.ctx.PartialCompositeContext.listBindings(PartialCompositeContext.java:330)
at com.sun.jndi.toolkit.ctx.PartialCompositeContext.listBindings(PartialCompositeContext.java:317)
at javax.naming.InitialContext.listBindings(InitialContext.java:472)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:136)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:145)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:110)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.lifecycleEvent(GlobalResourcesLifecycleListener.java:82)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:94)
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:401)
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:345)
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:784)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:152)
at org.apache.catalina.startup.Catalina.start(Catalina.java:655)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:355)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:495)
Similar questions and investigations suggest an invalid LDAP DN, but:
The same LDAP configuration works fine via an LDAP Client
No search is actually performed, at start-up time Tomcat throws this exception without any query
The error suggests an empty string '' as remaining name, hence not really something not found, apparently
Question(s): Is this the correct way to migrate an Foreign JNDI Providers entry from WebLogic to Tomcat? How to fix an invalid LDAP DN entry with an empty remaining name? Could it be a missing baseDN to configure somewhere?
Update
The same exact error happens when changing the LdapContextFactory to the following, as suggested via comments:
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
Hashtable<Object, Object> env = new Hashtable<>();
Reference reference = (Reference) obj;
Enumeration<RefAddr> references = reference.getAll();
String providerUrl = "no valid URL";
while (references.hasMoreElements()) {
RefAddr address = references.nextElement();
String type = address.getType();
String content = (String) address.getContent();
switch (type) {
case Context.PROVIDER_URL:
env.put(Context.PROVIDER_URL, content);
providerUrl = content;
break;
default:
env.put(type, content);
break;
}
}
InitialLdapContext context = null;
Object result = null;
try {
context = new InitialLdapContext(env, null);
LOGGER.info("looking up for " + providerUrl);
result = context.lookup(providerUrl);
} finally {
if (context != null) {
context.close();
}
}
LOGGER.info("Created new LDAP Context");
return result;
}
Change is confirmed via logging, to make sure it was deployed properly.
The involved listener is defined by default at the top of the server.xml file as
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
And cannot be disabled as per official documentation:
The Global Resources Lifecycle Listener initializes the Global JNDI resources defined in server.xml as part of the Global Resources element. Without this listener, none of the Global Resources will be available.
The same also happens on Tomcat version 8.5.5 and 7.0.69: simply adding the new global resource as above and the additional jar providing the factory above, the exception pointing at an empty remaining name will be thrown.
The stacktrace went away by appending to the java.naming.provider.url property the LDAP schema DN, using the first factory implementation provided in the question.
Below a screenshot of the LDAP client used in this context, the Apache Directory Studio / LDAP Browser embedded in Eclipse, from which it was possible to browse the concerned LDAP simply using the initial values of the question.
By appending the schema DN of the Root element to the connection URL, the exception went away and the LDAP resource is now shared via JNDI in Tomcat 8.
Further details as outcome of the troubleshooting:
In Tomcat 8 global resources are handled via a global resource listener, the GlobalResourcesLifecycleListener, defined by default in the server.xml file. Such a listener invokes a context.listBindings("") on bean creation, hence effectively browsing the LDAP directory.
This initial browsing may most probably be the difference between Tomcat and WebLogic, where LDAP is looked up via JNDI only when required, hence via direct query, rather than at start-up with a generic query. As such, in Tomcat the LDAP url would need further details, that is, a slightly different configuration as part of its url to directly point to a valid base DN.
From official WebLogic documentation:
On start up, WebLogic Server attempts to connect to the JNDI source. If the connection is successful, WebLogic Server sets up the requested objects and links in the local JNDI tree, making them available to WebLogic Server clients.
Hence, a connection is rather simpler than a listBindings:
Enumerates the names bound in the named context, along with the objects bound to them. The contents of any subcontexts are not included.
Related
I want to externalize password for Java #DataSourceDefinition using OS system variable ${appuserpwd}. The below is my #DataSourceDefinition
#DataSourceDefinition(
name = "java:app/jdbc/mydb",
className = "com.mysql.cj.jdbc.MysqlConnectionPoolDataSource",
portNumber = 3306,
serverName = "localhost",
databaseName = "mydb",
user = "appuser",
password = "${appuserpwd}",
isolationLevel = Connection.TRANSACTION_READ_COMMITTED,
properties = {})
I am using Wildfly 18. In subsystem ee, I already turn on these properties:
Annotation Property Replacement: ON
JBoss Descriptor Property Replacement: ON
Spec Descriptor Property Replacement: ON
I always get an Exception error as below:
Caused by: java.sql.SQLException: Access denied for user 'appuser'#'localhost' (using password: YES)
It means Wildfly failed to translate ${appuserpwd} to the real password from OS system environment named appuserpwd.
I have tried ${env.appuserpwd} for #DataSourceDefinition password but I got the same message.
If I replace ${appuserpwd} by the appuser real password -> The app works OK, no issues.
Any helps? Thanks!
There was a Java EE 7 spec proposal to support password aliasing, but it never made it into the specification. So there is no way to replace variables in a standard and portable (working on each Java EE compliant server) way.
Fortunately, the different application servers offer their own solution to achieve this.
For Wildfly you have to first enable annotation property replacement inside your standalone.xml:
<subsystem xmlns="urn:jboss:domain:ee:5.0">
<annotation-property-replacement>true</annotation-property-replacement>
</subsystem>
Now you can start replacing variables (syntax is ${ENV_VARIABLE:default}):
#DataSourceDefinition(
name = "java:app/jdbc/pqsql",
className = "org.postgresql.xa.PGXADataSource",
user = "${DB_USER:postgres}",
password = "${DB_PASSWORD:password}",
serverName = "${DB_SERVERNAME:localhost}",
portNumber = 5432,
databaseName = "${DB_DATABASENAME:testdatabase}")
You can find further information here.
UPDATE: I tried this with a recent Wildfly version (20.0.0.Final) and it seems there is a bug when replacing annotation variables. As a fallback you can use the traditional way of specifying the datasource using the Jboss CLI and use environment variables as expected:
# First create the new module for the JDBC driver
/subsystem=datasources/jdbc-driver=postgresql:add(driver-name=postgresql, driver-module-name=org.postgresql, driver-class-name=org.postgresql.Driver, driver-datasource-class-name=org.postgresql.ds.PGPoolingDataSource)
# Create a data source
/subsystem=datasources/data-source=PostgresDS:add(jndi-name=java:jboss/datasources/postgres, driver-name=postgresql, connection-url=jdbc:postgresl://localhost:5432/postgres, user-name=postgres, password=postgres)
Im working with Java backend using Tomcat and trying to connect to a hosted instance of couchbase. I have setup up the path to my config directory in ../tomcat/Catalina/localhost/context.xml.default
<Parameter name="CONFIG_DIRECTORY" value="/opt/platform/conf" override="false"/>
Also I have set a CLASSPATH param in ../tomcat/bin/setenv.sh
CLASSPATH=/opt/platform/conf/
Below is the snippet of code I am working with :
String initialNodes = RuntimeData.INSTANCE.getConfigurationValue("MYNODES");
String bucketId = RuntimeData.INSTANCE.getConfigurationValue("MYBUCKET");
System.out.println("Creating cluster for " + initialNodes);
try {
System.out.println("HOST INET ADDRESS : " + InetAddress.getByName(initialNodes));
} catch (UnknownHostException e) {
System.out.println("UNKNOWN HOST EXCEPTION : " + initialNodes);
}
cluster = CouchbaseCluster.create(initialNodes.split(","));
System.out.println("Creating bucket for " + bucketId);
bucket = cluster.openBucket(bucketId, 10, TimeUnit.SECONDS);
System.out.println("Creating graph.");
graph = new CBGraph();
In this code I have some debug logging and I can confirm that I do pull the correct values in for the initialNodes and the bucket. My issue currently comes in on the last line when I try to create a new CBGraph().
I get this error:
Caused by: com.couchbase.client.core.config.ConfigurationException: No valid node found to bootstrap from. Please check your network configuration.
My guess is that somehow the properties file that contains all of the connection info for my couchbase server is either not getting loaded into the classpath ... or it is getting loaded later than I need it too.
The only verification I have been able to do that adding the CLASSPATH setting to setenv.sh is that once tomcat is running I do see the path in classpath if I do ps auxw|grep tomcat.
Any help with this issue is welcome. I have looked at some other posts but im not sure exactly the issue im trying to solve here other than the error I get.
ADDITION:
Looking at the INFO logs at runtime I can verify that the dir containing my couchbase .properties file IS in the classpath. BUT ... it looks like couchbase is trying to initialize using default values (copied below)
INFO: Using the following configuration ...
Oct 24, 2016 3:08:09 PM com.couchbase.graph.conn.ConnectionFactory createCon
INFO: hosts = ubuntu-local
The adresses you are specifying might not be valid hostnames (or ip addresses), and you should also see one of these INFO log messages:
https://github.com/couchbase/couchbase-jvm-core/blob/4127377b8bd057ed291176d568a1e868796e4e5a/src/main/java/com/couchbase/client/core/message/cluster/SeedNodesRequest.java#L85-L102
I have some code like this for connecting to HornetQ.
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
properties.put(Context.PROVIDER_URL, "remote://127.0.0.1:4447");
properties.put(Context.SECURITY_PRINCIPAL, "user");
properties.put(Context.SECURITY_CREDENTIALS, "pwd");
ConnectionFactory connectionFactory = null;
Destination destination = null;
try {
Context context = new InitialContext(properties);
I inherited this, and am trying to get a better understanding of it. I haven't found documentation for the valid values where I have "remote://". I'm not sure if it's accurate to call that a protocol or not, but that's what it looks like. I've seen "jnp://" in other samples.
Is there an official list of valid values, and what they mean?
You may want to refer to specific JNDI Reference for specific versions. JBOSS AS 7.2 is covered here: https://docs.jboss.org/author/display/AS72/JNDI+Reference (note that in JBOSS AS 7.x, jnp is no longer supported, older JBOSS versions do support the jnp:// and access via the standard naming services).
Another link: https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Web_Platform/5/html/Administration_And_Configuration_Guide/Naming_on_JBoss-The_Naming_InitialContext_Factories.html.
This is my first question at Stack Overflow, so feel free to tell me if I do something wrong :)
I'm working on a project involving EJB and JBoss 4.2.3.GA. In a point, we try to access every node of the cluster, locating an EJB and returning it.
This is the piece of code that does the JNDI lookup:
public static <I> I getCache(Class<I> i, String clusterNode) {
ServiceLocator serviceLocator = ServiceLocator.getInstance();
String jndi = serviceLocator.getRemoteJNDIName(i);
Properties props = new Properties();
props.setProperty(Context.PROVIDER_URL, "jnp://" + clusterNode + ":"
+ jndiPort);
props.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming");
props.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.jnp.interfaces.NamingContextFactory");
Object result = null;
try {
InitialContext ctx = new InitialContext(props);
result = ctx.lookup(jndi);
} catch (NamingException e) {
return null;
}
return (I) result;
}
Here:
clusterNode is a simple string with the IP address or the dns name of the node. E.g: "192.168.2.65" or "cluster1".
getRemoteJNDIName returns an String such as this: "MyEARName/MyEJBName/remote"
The problem is that when I call this method with, for example, "127.0.0.1" it works fine. Also, if I call it with an existing and working IP address where a server is up and running, it's OK too.
However if I call the method with a non-existing or non-working address or dns name, instead of throwing the NamingException, it returns the EJB in my own machine. Therefore, I don't know wether the node is up or not.
I guess there may be better ways to do it. I'd like to hear about them, but we cannot make "big" changes to the product due to it being in production for a few years by now.
That's it. Thank you in anticipation and best regards.
However if I call the method with a non-existing or non-working
address or dns name, instead of throwing the NamingException, it
returns the EJB in my own machine
I think this behavior can be explained if you have automatic naming discovery. When the Contex.PROVIDER_URL is not specified or the nodes in the list are not reachable (which is your case) the client is allowed to search in the network for available JNDI services.
However this only works under certain conditions, some of them:all clusters node running in ALL mode, all nodes located in the same subnet.
You can disable this behavior setting the InitialContext property jnp.disableDiscovery=true.
I guess there may be better ways to do it
According to the code, you are not catching the object polled from the JNDI, this implies that every time you need to execute a service, a new lookup (wich is a time consuming operation) has to be done. The ServiceLocator pattern suggests caching the lookup result for improving performance.
My application is an Spring framework 3.1.2 based Web-application deployed on Apache tomcat 6.
I need to get my running application port-number and host-name on application(server) Start-up. So that I would override it on a property and it is need for Other bean initialization.
Does the spring provides any options to retrieves these details and to set it on Server Startup?..
Take a look into those two questions previously asked: Get the server port number from tomcat with out a request and I need to know the HTTP and HTTPS port my java webapp is running on webapp startup. There you will see how to get port from a Connector, connector also has getDomain method that would give you host name.
Since you know how to get without spring, you can have a bean that gets those details and provides them for the other bean that needs those instantiation detail. There are few ways to do that:
1) Create Spring factory bean that would get port, hostname and instantiates bean you want
2) Have separate bean that holds those details for you and you use that bean to construct other one
3) You override your application details with port and domain and when instantiating bean that needs them have a init method that would read them for your new bean
Here is code for getting port an dip address
class IPAddressDemo{
public static String getIpAddressAndPort1() throws MalformedObjectNameException, NullPointerException,
UnknownHostException {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"),
Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
String host = InetAddress.getLocalHost().getHostAddress();
String port = objectNames.iterator().next().getKeyProperty("port");
String ipadd = "http" + "://" + host + ":" + port;
System.out.println(ipadd);
return ipadd;
}
}
Tomcat will run by default on TCP/IP port number 8080.
Steps
Navigate to C:\apache-tomcat-6.0.18\conf\server.xml (The place where you have installed tomcat)
In server.xml file, find Connector port looking like the following
<connector port="8080" protocol="HTTP/1.1" connectiontimeout="20000" redirectport="8443">
</connector>
port in the Connector tag is your port no.
Finding hostnames:
Steps
1. Navigate to `C:\WINDOWS\system32\drivers\etc`
Or
start->All Programs->Run-> type 'drivers' (Without quotes)->etc
Open the file host with a text editor and you can find
127.0.0.1 localhost
From this you can understand what your hostname is.
Thanks.
The ServletRequest object that has been passed to your doGet, or doPost method has getServerName() and getServerPort() methods that provide this information.
Example:
public void doGet(ServletRequest request, ServletResponse response) {
System.out.println("Host Name = " + request.getServerName());
System.out.println("Port Number = " + request.getServerPort());
}