I'm facing a NoClassDefFoundError for com.sap.aii.security.lib.exception.SecurityException in my EJB project, which is deployed on SAP NetWeaver. Here are the details:
Project Setup:
Problem:
SAPSecurityResources secRes = SAPSecurityResources.getInstance();
Here's the error stack trace:
javax.ejb.EJBException: nested exception is: java.lang.RuntimeException: java.lang.NoClassDefFoundError: com/sap/aii/security/lib/exception/SecurityException
...
Caused by: java.lang.ClassNotFoundException: com.sap.aii.security.lib.exception.SecurityException
...
Loader Info:
ClassLoader name: [sap.com/myclient2partyLdap-ear-jp-library-loader]
...
/usr/sap/PO1/J00/j2ee/cluster/apps/sap.com/myclient2partyLdap-ear-jp/app_libraries_container/lib/com.sap.security_2.0.0.201214143317.jar
According to the com.sap.aii.af.service.resource > Class SAPSecurityResources Javadoc, only com.sap.aii.af.svc.facade jar file is needed to make an instance of SAPSecurityResources object if you want to access KeyStoreManager and manage certificates, but somehow my code doesn't run.
Any help or suggestions to resolve this issue would be greatly appreciated.
Thank you!
Steps I Have Taken:
This is usually sufficient to add jar files in the buildpath or in Deployment Assembly and that is why I was not expecting this error at the least. I do not even access the mentioned class directly in the code. The code does not give any "unresolved reference" error.
P.S I have accessed keystore views in java mappings using the exact same API but in the setup mentioned above I get the NoClassDefFoundError.
In a JavaMapping in SAP PI when you want to access KeyStoreManager you do it using Class SAPSecurityResources in com.sap.aii.af.svc.facade API found in PO by default.
If you want to access java keystore in J2EE applications you cannot use SAPSecurityResources for some reason and it keeps generating the NoClassDefFoundError Exception. You can access keystore manager using JNDI lookup using javax.naming.Context (Code below). However it doesn't return the KeyStoreManager interface found in SAPSecurityResources and thus you cannot use com.sap.aii.af.lib.facade API to get a reference to KeyStoreManager.
When you want to access KeyStoreManager from javax.naming.Context object using lookup (as below) it returns com.sap.engine.services.keystore.interfaces.KeystoreManagerWrapper interface.
Example Code
Hashtable <String, String> env = new Hashtable<>();
// Replace with your actual configuration
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sap.engine.services.jndi.InitialContextFactoryImpl");
env.put(Context.PROVIDER_URL, "hostname:50004"); // P4 port for JNDI lookup
env.put(Context.SECURITY_PRINCIPAL, "username"); // Replace with your actual username
env.put(Context.SECURITY_CREDENTIALS, "password"); // Replace with your actual password
env.put(Context.URL_PKG_PREFIXES, "com.sap.engine.services");
try {
Context jndiContext = new InitialContext(env);
KeystoreManagerWrapper ksManager = (KeystoreManagerWrapper) jndiContext.lookup("keystore");
// Use the ksManager as needed
} catch (NamingException e) {
e.printStackTrace();
}
You can then use the object as needed, for example
// Retrieve and log aliases
List<String> aliases = ksManager.aliases("alias");
System.out.println("Aliases found: " + aliases.size());
for (String alias : aliases) {
System.out.println("Alias: " + alias);
}
background:
When you feed in your env variable to InitialContext it does return an object, so with a little help from the internet I analyzed the object:
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.lang.reflect.Method;
import java.util.Hashtable;
public class JNDIExample {
public static void main(String[] args) {
Hashtable < String, String > env = new Hashtable < > ();
// Replace with your actual configuration
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sap.engine.services.jndi.InitialContextFactoryImpl");
env.put(Context.PROVIDER_URL, "hostname:50004"); // P4 port for JNDI lookup
env.put(Context.SECURITY_PRINCIPAL, "username"); // Replace with your actual username
env.put(Context.SECURITY_CREDENTIALS, "password"); // Replace with your actual password
env.put(Context.URL_PKG_PREFIXES, "com.sap.engine.services");
try {
Context jndiContext = new InitialContext(env);
Object obj = jndiContext.lookup("keystore"); // Replace with your actual JNDI name
// Check if lookup returned a null object
if (obj == null) {
System.out.println("Lookup returned null");
return;
}
// Print the class of the returned object
Class < ? > objClass = obj.getClass();
System.out.println("Class of the returned object: " + objClass.getName());
// Print the interfaces implemented by the returned object
System.out.println("Interfaces implemented by the returned object:");
Class < ? > [] interfaces = objClass.getInterfaces();
for (Class < ? > iface : interfaces) {
System.out.println(" - " + iface.getName());
}
// Print the methods available in the returned object's class
System.out.println("Methods available in the returned object's class:");
Method[] methods = objClass.getMethods();
for (Method method: methods) {
System.out.println(" - " + method.getName());
}
} catch (NamingException e) {
e.printStackTrace();
}
}
}
The code above prints the following log:
/*
Interfaces implemented by the returned object:
- com.sap.engine.services.keystore.interfaces.KeystoreManagerWrapper
- com.sap.engine.services.rmi_p4.RemoteRef
- java.rmi.Remote
Methods available in the returned object's class:
- equals
- destroyKeystoreView
- renameEntry
- getObjectInfo
- checkPermission
- toString
- getSecurityConnector
- getKeystore
- getKeystoreViewAliases
- aliases
- findAlias
- existKeystoreView
- createKeystoreView
- hashCode
- size
- readEntry
- setProperty
- getProperty
- getKeystoreViewProperties
- writeEntry
- exists
- removeProperty
- deleteEntry
- isKeyEntry
- getProxyClass
- newProxyInstance
- isProxyClass
- getInvocationHandler
- getClass
- notify
- notifyAll
- wait
- wait
- wait
*/
The location of the API containing the interface KeystoreManagerWrapper :
/usr/sap/<SID>/J<instance_number>/j2ee/cluster/bin/interfaces/keystore_api/lib/private/sap.com~tc~je~keystore_api~API.jar in OS.
update with your <SID> and J<instance_number>
Download the API File using plain java or just ask basis team at this point ;).
Example Code
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/downloadFile")
public class FileDownloadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Full path to the file on the server
String filePath = "/usr/sap/<SID>/J<instance_number>/j2ee/cluster/bin/interfaces/keystore_api/lib/private/sap.com~tc~je~keystore_api~API.jar";
File file = new File(filePath);
// Check if the file exists and is not a directory
if (file.exists() && file.isFile()) {
// Set the content type based on the file type
response.setContentType("application/java-archive");
response.setHeader("Content-Disposition", "attachment;filename=" + file.getName());
// Read the file and write it to the response output stream
try (FileInputStream fis = new FileInputStream(file); OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
}
} else {
response.getWriter().println("File not found: " + filePath);
}
}
}