servletsjavabeansnetweaversap-pi

Resolving NoClassDefFoundError for com.sap.aii.security.lib.exception.SecurityException in SAP NetWeaver PO EJB Project Configuration along with DWP


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:

  1. Added JARs to the EAR lib directory: Placed the required JAR files in the lib directory of the EAR project.
  2. Verified EAR Deployment Assembly: Ensured that the lib directory is included in the deployment assembly of the EAR project.
  3. Configured EJB and Web Projects: Added the same JAR files to the build path of both the EJB and DWP projects in Eclipse.

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.


Solution

  • 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);
            }
        }
    }