Search this blog ...

Tuesday, July 17, 2012

File Download Java Servlet example - 2GB overflow workaround

I discovered a few days back an issue with our product on HTTP downloads > 2GB.  It appears to be a simple overflow on the HttpServletResponse.setContentLength method. You can probably excuse the API designers circa 1997 assuming a 32-bit signed Integer with max value 231-1 (2147483647 bytes) would be sufficient.  The Gigabit ethernet standard did not come for another year (1998)!

Here is the exception seen when you provide a long value greater than 2147483647 bytes to the setContentLength(int) method:

java.net.ProtocolException: Exceeded stated content-length of: '-XXXX' bytes
        at weblogic.servlet.internal.ServletOutputStreamImpl.checkCL(ServletOutputStreamImpl.java:200)

Below is a sample download servlet with workaround for the 2gb limitation.  It has been tested on Firefox 3.6 against WebLogic Server 10.3.6 with a 2.2GB download and worked perfectly.


import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class DownloadServlet
  extends HttpServlet
{
  @SuppressWarnings("compatibility:1533750721037291976")
  private static final long serialVersionUID = 1L;

  protected void doGet(HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException
  {
    doPost(request, response);
  }

  protected void doPost(HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException
  {
    // if no file parameter specified, download hosts file
    String file = request.getParameter("file");
    file = (file == null || file.length() == 0) ? "/etc/hosts" : file;

    File fileObj = new File(file);
    if ((!fileObj.exists()) || (!fileObj.isFile()) || (!fileObj.canRead()))
    {
      throw new IOException("'file' '" + file + "' cannot be read.");
    }

    ServletContext context = getServletConfig().getServletContext();

    String mimetype = context.getMimeType(file);
    response.setContentType(mimetype == null ? "application/octet-stream" :
        mimetype);

    long length = fileObj.length();
    if (length <= Integer.MAX_VALUE)
    {
      response.setContentLength((int)length);
    }
    else
    {
      response.addHeader("Content-Length", Long.toString(length));
    }

    response.setHeader("Content-Disposition",
        "attachment; filename=\"" + fileObj.getName() + "\"");

    ServletOutputStream out = response.getOutputStream();
    InputStream in = null;
    byte[] buffer = new byte[32768];
    try
    {
      in = new FileInputStream(fileObj);

      int bytesRead;
      while ((bytesRead = in.read(buffer)) >= 0)
      {
        out.write(buffer, 0, bytesRead);
      }
    }
    finally
    {
      if (in != null)
      {
        in.close();
      }
    }
  }
}

Wednesday, June 27, 2012

Decrypt / Dump contents of CWALLET.SSO (Oracle file based credential store)

When using a file-based credential store with Oracle, credentials ultimately get stored in a wallet file (cwallet.sso)

Very little if any info exists on how to dump the contents of the wallet.  At best, most people leverage the trusty orapki command to get an overview of what’s inside as far as the maps and keys, but actual password information is never divulged.

For example:

$MW_HOME/oracle_common/bin/orapki wallet display -wallet ~/cwallet.sso
Oracle PKI Tool : Version 11.1.1.6.0
Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved.

Requested Certificates:
User Certificates:
Oracle Secret Store entries:
dip@#3#@cn=odisrv
ODSMMap@#3#@ODSMKey.Wallet
oracle.wsm.security@#3#@enc-csf-key
oracle.wsm.security@#3#@keystore-csf-key
oracle.wsm.security@#3#@sign-csf-key
Trusted Certificates:
Subject:        OU=Class 1 Public Primary Certification Authority,O=VeriSign\, Inc.,C=US
Subject:        OU=Secure Server Certification Authority,O=RSA Data Security\, Inc.,C=US
Subject:        CN=Entrust.net Secure Server Certification Authority,OU=(c) 1999 Entrust.net Limited,OU=www.entrust.net/CPS incorp. by ref. (limits liab.),O=Entrust.net,C=US
Subject:        CN=GTE CyberTrust Global Root,OU=GTE CyberTrust Solutions\, Inc.,O=GTE Corporation,C=US
Subject:        OU=Class 3 Public Primary Certification Authority,O=VeriSign\, Inc.,C=US
Subject:        CN=Entrust.net Secure Server Certification Authority,OU=(c) 2000 Entrust.net Limited,OU=www.entrust.net/SSL_CPS incorp. by ref. (limits liab.),O=Entrust.net
Subject:        OU=Class 2 Public Primary Certification Authority,O=VeriSign\, Inc.,C=US
Subject:        CN=Entrust.net Certification Authority (2048),OU=(c) 1999 Entrust.net Limited,OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.),O=Entrust.net

Until now that is :)

Note - wallet and jps-config-dump file must reside in same location for this sample code to function!!  In the example below, choose either the domain wallet, or the bootstrap wallet.

PATH_TO_WALLET="$DOMAIN_HOME/config/fmwconfig/bootstrap/cwallet.sso"
PATH_TO_JPS="$DOMAIN_HOME/config/fmwconfig/bootstrap/jps-config-dump.xml"

or …

PATH_TO_WALLET="$DOMAIN_HOME/config/fmwconfig/cwallet.sso"
PATH_TO_JPS="$DOMAIN_HOME/config/fmwconfig/jps-config-dump.xml"

cat > "${PATH_TO_JPS}" <<EOF
<?xml version="1.0" encoding="UTF-8" standalone='yes'?>
<jpsConfig xmlns="http://xmlns.oracle.com/oracleas/schema/11/jps-config-11_1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/oracleas/schema/11/jps-config-11_1.xsd
jps-config-11_1.xsd" schema-major-version="11" schema-minor-version="1">
   <serviceProviders>
      <serviceProvider type="CREDENTIAL_STORE" name="credstoressp" class="oracle.security.jps.internal.credstore.ssp.SspCredentialStoreProvider">
         <description>Credential Store Service Provider</description>
      </serviceProvider>
   </serviceProviders>
   <serviceInstances>
      <serviceInstance provider="credstoressp" name="credstore">
         <property value="file:${PATH_TO_WALLET}" name="location"/>
      </serviceInstance>
   </serviceInstances>
  <jpsContexts default="test">
    <jpsContext name="test">
      <serviceInstanceRef ref="credstore"/>
    </jpsContext>
  </jpsContexts>
</jpsConfig>
EOF

cat > /tmp/DumpWallet.java <<EOF
import java.io.File;

import java.util.Hashtable;

import oracle.security.jps.JpsContext;
import oracle.security.jps.JpsContextFactory;

import oracle.security.jps.service.credstore.Credential;
import oracle.security.jps.service.credstore.CredentialFactory;
import oracle.security.jps.service.credstore.CredentialMap;
import oracle.security.jps.service.credstore.CredentialStore;
import oracle.security.jps.service.credstore.GenericCredential;
import oracle.security.jps.service.credstore.PasswordCredential;

public class DumpWallet
{
  private static final byte[] HEX = new byte[] {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 
 
  public static void main(String[] args)
  {
    try
    {
      System.setProperty("oracle.security.jps.config", (args.length > 0) ? args[0] : "jps-config.xml");
      // System.setProperty("java.security.debug", "all");
      JpsContextFactory ctxFactory  = JpsContextFactory.getContextFactory();
      JpsContext ctx = ctxFactory.getContext();
      CredentialStore store = ctx.getServiceInstance(CredentialStore.class);
      listAll(store);
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
 
  private static void listAll(CredentialStore store) throws Exception
  {
    System.out.println("Dumping store contents ...");
    for (String map : store.getMapNames())
    {
      System.out.println("\n" + "### Map: " + map);
      CredentialMap credMap = store.getCredentialMap(map);
      if (credMap != null)
      {
        int i = 1;
        for (String key : credMap.keySet())
        {
          System.out.println(" " + i++ + ". + Key: " + key);
          
          Credential cred = credMap.getCredential(key);
          System.out.println("  class = " + cred.getClass().getName());
          System.out.println("  desc  = " + cred.getDescription());

          if (cred instanceof PasswordCredential)
          {
            PasswordCredential pc = (PasswordCredential)cred;
            System.out.println("  name  = " + pc.getName());
            System.out.println("  pass  = " + new String(pc.getPassword()));
            System.out.println("  expires   = " + pc.getExpiryTime());
          }
          else if (cred instanceof GenericCredential)
          {
            GenericCredential gc = (GenericCredential)cred;
            Object c = gc.getCredential();
            String type = (! c.getClass().isArray())
              ? c.getClass().getName()
              : ("Array of " + c.getClass().getComponentType().getName());

            System.out.println("  type  = " + type);
            if (c instanceof String)
            {
              System.out.println("  cred  = " + c);
            }
            else if ( c instanceof Hashtable)
            {
              Hashtable ht = (Hashtable)c;
              for (Object htkey : ht.keySet())
              {
                Object htVal = ht.get(htkey);
                if (htVal instanceof char[])
                {
                  System.out.println("  cred  = (" + htkey + ", " + new String((char[])htVal) + ")");
                }
                else
                {
                  System.out.println("  cred  = (" + htkey + ", " + htVal + ")");
                }
              }
            }
            else if (c instanceof javax.crypto.spec.SecretKeySpec)
            {
              javax.crypto.spec.SecretKeySpec secret = (javax.crypto.spec.SecretKeySpec) c;
              System.out.println("  algorith  = " + secret.getAlgorithm());
              System.out.println("  format  = " + secret.getFormat());
              System.out.println("  key material as hex = " + bytesAsHex(secret.getEncoded()));
            }
            else if (c instanceof byte[])
            {
              System.out.println("  byte array as hex = " + bytesAsHex((byte[])c));
            }
            System.out.println("  expires   = " + gc.getExpiryTime());
          }
          else
          {
            System.out.println("  toStr = " + cred.toString());
          }
        }
      }
    }
  }

  public static final String bytesAsHex(byte[] bytes)
  {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < bytes.length; i++)
    {
      sb.append((char)(HEX[(bytes[i] & 0x00F0) >> 4])).append((char)(HEX[bytes[i] & 0x000F])).append(" ");
    }
    return sb.toString();
  }
}
EOF

CP=/tmp
CP=$CP:$MW_HOME/oracle_common/modules/oracle.jps_11.1.1/jps-api.jar
CP=$CP:$MW_HOME/oracle_common/modules/oracle.jps_11.1.1/jps-common.jar
CP=$CP:$MW_HOME/oracle_common/modules/oracle.jps_11.1.1/jps-internal.jar
CP=$CP:$MW_HOME/oracle_common/modules/oracle.idm_11.1.1/identitystore.jar
CP=$CP:$MW_HOME/oracle_common/modules/oracle.osdt_11.1.1/osdt_xmlsec.jar
CP=$CP:$MW_HOME/oracle_common/modules/oracle.pki_11.1.1/oraclepki.jar

$JAVA_HOME/bin/javac -cp $CP /tmp/DumpWallet.java

$JAVA_HOME/bin/java -cp $CP DumpWallet "${PATH_TO_JPS}"

Sample Output :-

Dumping store contents ...

### Map: oracle.wsm.security
1. + Key: sign-csf-key
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = signing key alias/password
  name  = orakey
  pass  = welcome1
  expires   = null
2. + Key: enc-csf-key
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = encryption key alias/password
  name  = orakey
  pass  = welcome1
  expires   = null
3. + Key: keystore-csf-key
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = keystore access password
  name  = n/a
  pass  = welcome1
  expires   = null
4. + Key: test-appid-key
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = null
  name  = weblogic
  pass  = welcome1
  expires   = null

### Map: IDCCS
1. + Key: ldap:1340771431089
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = null
  name  = ldap:1340771431089
  pass  = MjcxN0M2ODREOEQ0RjZERg==
  expires   = null
2. + Key: db:1340771431083
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = null
  name  = db:1340771431083
  pass  = MTlDQTE1N0EzQzE3REY1OA==
  expires   = null
3. + Key: hash:1340771431089
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = null
  name  = hash:1340771431089
  pass  = MTk1MTk1QkQ4OUE2QzJBNw==
  expires   = null
4. + Key: proxy:1340771431089
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = null
  name  = proxy:1340771431089
  pass  = M0IwMUZBQjNGQTUxNzk0OA==
  expires   = null

In a WebLogic domain, you will find that there is also a cwallet.sso found in $DOMAIN_HOME/config/fmwconfig/bootstrap.

Dumping store contents ...

### Map: fks
1. + Key: master.key.0
  class = oracle.security.jps.internal.credstore.GenericCredentialImpl
  desc  = null
  type  = javax.crypto.spec.SecretKeySpec
  algorith  = AES
  format  = RAW
  key material as hex = CB 45 4F B0 F8 26 FF 04 31 9F 48 DD 43 42 69 C7
  expires   = null
2. + Key: current.key
  class = oracle.security.jps.internal.credstore.GenericCredentialImpl
  desc  = null
  type  = java.lang.String
  cred  = master.key.0
  expires   = null

### Map: IntegrityChecker
1. + Key: kss
  class = oracle.security.jps.internal.credstore.GenericCredentialImpl
  desc  = null
  type  = Array of byte
  byte array as hex = AB 18 CD 76 6C 39 FE 46 A0 6D 1C F0 BC 8D 97 3A D1 64 BC 80 2C 33 64 8E AE C9 B1 63 88 BE 23 7C 37 2F 63 9D 55 2B 5E 8F 1E 08 0A 73 F1 A8 15 83 8F 24 3D 19 B8 79 6E 75 B2 1C 7F DB 72 FC AE BA 72 A3 62 62 27 29 EE DE
  expires   = null

If you ever decide to reassociate the credential store with LDAP (e.g. using the reassociateSecurityStore command), you will find that the bootstrap cwallet.sso will contain credentials to access the LDAP store.  Here is what my domain wallet files look like when using an LDAP credential store :-

$DOMAIN_HOME/config/fmwconfig/cwallet.sso :

Dumping store contents ...

### Map: dip
1. + Key: cn=odisrv
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = DIP Password
  name  = cn=odisrv,cn=Registered Instances,cn=Directory Integration Platform,cn=Products,cn=OracleContext
  pass  = YNDMSU1wP1WergcX
  expires   = null

### Map: ODSMMap
1. + Key: ODSMKey.Wallet
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = ODSM Key store password
  name  = ODSM
  pass  = 0000000000
  expires   = null

$DOMAIN_HOME/config/fmwconfig/bootstrap/cwallet.sso :

Dumping store contents ...

### Map: BOOTSTRAP_JPS
1. + Key: bootstrap_q6ShJcm89vO8N2oVoSFqTLuW6Sg=
  class = oracle.security.jps.internal.credstore.PasswordCredentialImpl
  desc  = bootstrap user name and password
  name  = cn=orcladmin
  pass  = welcome1
  expires   = null

### Map: fks
1. + Key: master.key.0
  class = oracle.security.jps.internal.credstore.GenericCredentialImpl
  desc  = null
  type  = javax.crypto.spec.SecretKeySpec
  algorith  = AES
  format  = RAW
  key material as hex = 1C B5 89 2A 45 F2 BA A0 E5 C1 A8 F6 DE 6E FC 5A
  expires   = null
2. + Key: current.key
  class = oracle.security.jps.internal.credstore.GenericCredentialImpl
  desc  = null
  type  = java.lang.String
  cred  = master.key.0
  expires   = null

### Map: IntegrityChecker
1. + Key: kss
  class = oracle.security.jps.internal.credstore.GenericCredentialImpl
  desc  = null
  type  = Array of byte
  byte array as hex = FD 01 1E 54 D5 84 3B 9D AF DA 62 62 22 BB 7E A9 0C DB 08 A3 D9 71 9F A6 03 96 7F DF 29 69 37 55 60 5D 0E 32 EE 3A D0 D6 F2 A9 FD 58 DB 82 87 A0 98 D2 78 6A 47 48 E9 6B 86 3E 68 77 BC 17 01 B6 A0 BD 29 A2 3B E7 B7 73
  expires   = null

 

Have fun!

JDeveloper - RIDC JAX/WS SAML Connections to UCM / Content Server

I posted a few years back some really useful information on configuring the infrastructure of the JDeveloper 11g embedded WebLogic server to allow consumption of a JAX/WS Web Service protected by a WSM policy:

http://todayguesswhat.blogspot.com.au/2010/09/jdeveloper-11gr1-integrated-weblogic.html

Today I will extend this information with some useful tips for connecting an application that is deployed to the JDeveloper embedded WebLogic server to connect to a back-end Oracle Content Server (aka Oracle UCM; aka Oracle WebCenter Content Server) using JAX/WS protocol.

Firstly, some background on the configuration of the UCM Server (11.1.1.6.0) and its associated domain:

The UCM Server is deployed to a WebLogic Domain (which I now will refer to as the UCM Domain) configured with WSM policy manager; Such that, the oracle.wsmpm_template_11.1.1.jar template has been applied and subsequently configured to connect to the MDS schema installed by the RCU.

The UCM Domain has been configured with an OID Authentication Provider.

A keystore has been created for the UCM Domain and populated with key-certificate pair signed by the demo certificate authority (CA) that ships with WebLogic.  Note – don’t use this CA in production!

oracle.wsm.security credential store entries have been created for the UCM Domain with the credentials / aliases required to access the keystore contents from above.

GPA Policy has also been configured on the UCM Domain for “ws-service” using message protection service policy oracle/wss11_saml_or_username_token_with_message_protection_service_policy which supports username/password connections and saml connections.

Below are some sample script calls that you can leverage to achieve the above UCM server configuration (assuming Linux):

cd $DOMAIN_HOME/config/fmwconfig

# Create service key-certificate pair signed by the demo CA cert "CertGenCA" with key password welcome1
java utils.CertGen -certfile MyPublicCert -keyfile MyPrivateKey -keyfilepass welcome1 -cn "`hostname -f`"

# Create and populate new service keystore default-keystore.jks with service key-certificate pair from above under alias orakey with keystore password welcome1, and alias password welcome1
java utils.ImportPrivateKey -keystore default-keystore.jks -storepass welcome1 -certfile MyPublicCert.der -keyfile MyPrivateKey.der -keyfilepass welcome1 -alias orakey -keypass welcome1

# Now add the root CA to the service keystore under the alias name CA
keytool -importcert -noprompt -alias CA -file $WL_HOME/server/lib/CertGenCA.der -keystore default-keystore.jks -storepass welcome1

$MW_HOME/oracle_common/common/bin/wlst.sh

wls_username="weblogic"
wls_password="welcome1"
wls_url="t3://localhost:7001"
connect(wls_username, wls_password, wls_url)

createCred(map="oracle.wsm.security", key="keystore-csf-key", user="n/a", password="welcome1", desc="keystore access password")
createCred(map="oracle.wsm.security", key="sign-csf-key", user="orakey", password="welcome1", desc="signing key alias/password")
createCred(map="oracle.wsm.security", key="enc-csf-key", user="orakey", password="welcome1", desc="encryption key alias/password")

serverConfig()

domain_name=cmo.getName()
policy_set_name=domain_name+"-ws-service"
policy_type="ws-service"
resource='Domain("' + domain_name + '")'
policy="oracle/wss11_saml_or_username_token_with_message_protection_service_policy"

beginRepositorySession()
createPolicySet(policy_set_name, policy_type, resource)
attachPolicySetPolicy(policy)
validatePolicySet()
commitRepositorySession()
disconnect()
exit()

Note above, we have leveraged a Message Protection service policy.  If we were to switch to a non-Message Protection service policy such as oracle/wss_saml_or_username_token_service_policy life would be much simpler.  However you need to be extremely careful with deploying such a policy in your enterprise.  You need to understand the consequences, as you may be creating yourself an easily exploitable security hole!!!

The policy oracle/wss_saml_or_username_token_service_policy does NOT do signing/encryption, certificates are NOT used!.
Thus the client does not need a keystore configured.  Also, setting up trusted client DN for the issuer will have no affect.
This profile is in no way shape or form secure and should only be used on a closed internal network where no rogue client can exist.
A rogue client can simply leverage the client policy oracle/wss10_saml_token_client_policy and set BindingProvider.USERNAME_PROPERTY to any user they like and make a connection to the service. 

I exploit this backdoor all the time here at Oracle with our fusion test / WAR room environments.  Effectively, you get an EVP-level visibility bug filed against you (which 98% of time is not a bug – at least in my code ;) ) – and you are forced to troubleshoot with a read-only account (if you are lucky) or some minimal obscure log messages.  Using this trick, I just check the UCM login service to see what policy is applied, and if no message protection policy is defined, I go get myself an admin connection and find out what going on before 92ing the bug!

Anyway, back to the topic at hand.  We are using a Message Protection policy, so the client must have a valid keystore and key-certificate pair in use, and the client’s public certificate must be present in the server’s keystore.  The simplest mechanism in a non-security hardened test environment to achieve this requirement is to simply copy/clone the server’s keystore file to the client, and create identical credential store entries for the oracle.wsm.security properties.  In security hardened environments though, each client domain would have a unique signing/encryption keypair, and the client's associated public certificate must be manually imported in to the server domain's keystore.

Let’s proceed with the JDeveloper embedded WebLogic client steps based on simple cloning of the server’s keystore! If you have not already created the default domain in JDeveloper, do so now, and fire up the server!  I’m running Oracle JDeveloper 11g Release 2 (11.1.2.1.0) on Windows XP.

1. From the JDeveloper View menu, choose Application Server Navigator
2. Right click on Application Servers / IntegratedWebLogicServer, select "Create Default Domain..."
2. Right click on Application Servers / IntegratedWebLogicServer, select "Start Server Instance"

Step 1) Copy from the UCM server its default-keystore.jks file.

/u01/app/oracle/product/Middleware/user_projects/domains/base_domain/config/fmwconfig/default-keystore.jks

Copy this file to the %DOMAIN_HOME%\config\fmwconfig directory of your jdeveloper

e.g. from Command Prompt:

SET DOMAIN_HOME=C:\JDeveloper\system11.1.2.1.38.60.81\DefaultDomain
%DOMAIN_HOME%\bin\setDomainEnv.cmd
cd %DOMAIN_HOME%\config\fmwconfig
## ftp binary get from UCM server the following file
/u01/app/oracle/product/Middleware/user_projects/domains/base_domain/config/fmwconfig/default-keystore.jks

Step 2) Create credential store entries to access keys/certificates from keystore file above (make sure your domain is running!)

%MW_HOME%\oracle_common\common\bin\wlst
connect('weblogic','welcome1','t3://127.0.0.1:7101')
createCred(map="oracle.wsm.security", key="keystore-csf-key", user="n/a", password="welcome1")
createCred(map="oracle.wsm.security", key="sign-csf-key", user="orakey", password="welcome1")
createCred(map="oracle.wsm.security", key="enc-csf-key", user="orakey", password="welcome1")
exit()

Step 3) Create GPA ws-client policy

%MW_HOME%\oracle_common\common\bin\wlst
connect('weblogic','welcome1','t3://127.0.0.1:7101')

serverConfig()

domain_name=cmo.getName()
policy_set_name=domain_name+"-ws-client"
policy_type="ws-client"
resource='Domain("' + domain_name + '")'
policy="oracle/wss11_saml_token_with_message_protection_client_policy"

beginRepositorySession()
createPolicySet(policy_set_name, policy_type, resource)
attachPolicySetPolicy(policy)
validatePolicySet()
commitRepositorySession()
disconnect()
exit()

Step 4) Configure JDeveloper Embedded WebLogic Domain to use OID Authentication Provider and OID server leveraged by the UCM server.

%MW_HOME%\oracle_common\common\bin\wlst
connect('weblogic','welcome1','t3://127.0.0.1:7101')

edit()
startEdit()
securityRealm = cmo.getSecurityConfiguration().getDefaultRealm();
print("Realm: " + securityRealm.getName())

oid_provider_name="OIDAuthenticator"
oid_host="xxx.us.oracle.com"
oid_port=3060
oid_user="cn=orcladmin"
oid_user_pwd="welcome1"
oid_userbase="cn=Users,dc=us,dc=oracle,dc=com"
oid_groupbase="cn=Groups,dc=us,dc=oracle,dc=com"

oidAP = securityRealm.createAuthenticationProvider(oid_provider_name,"weblogic.security.providers.authentication.OracleInternetDirectoryAuthenticator")
oidAP.setHost(oid_host)
oidAP.setPort(oid_port)
oidAP.setPrincipal(oid_user)
oidAP.setCredential(oid_user_pwd)
oidAP.setUserBaseDN(oid_userbase)
oidAP.setAllUsersFilter("(&(uid=*)(objectclass=person))")
oidAP.setUserNameAttribute("uid")
oidAP.setUseRetrievedUserNameAsPrincipal(true)
oidAP.setGroupBaseDN(oid_groupbase)
oidAP.setControlFlag("SUFFICIENT")

print("Provider order (existing) : ")
print securityRealm.getAuthenticationProviders()
daAP = securityRealm.lookupAuthenticationProvider("DefaultAuthenticator")
diaAP = securityRealm.lookupAuthenticationProvider("DefaultIdentityAsserter")
securityRealm.setAuthenticationProviders([oidAP,daAP,diaAP])
print("Provider order (updated) : ")
print securityRealm.getAuthenticationProviders()

print("Default Authenticator Control Flag (existing) : " + daAP.getControlFlag())
daAP.setControlFlag("SUFFICIENT")
print("Default Authenticator Control Flag (updated) : " + daAP.getControlFlag())

save()
activate(block="true")
disconnect()
exit()

Step 5) Optional - update WLS admins ...

%MW_HOME%\oracle_common\common\bin\wlst
connect('weblogic','welcome1','t3://127.0.0.1:7101')

securityRealm = cmo.getSecurityConfiguration().getDefaultRealm()
roleMapper = securityRealm.lookupRoleMapper("XACMLRoleMapper")
existing = roleMapper.getRoleExpression(None,"Admin")
print("WLS Admins (existing) : " +  existing)

ecm_domain_admin_oid_group_cn="ECM Domain Administrators"

roleMapper.setRoleExpression(None,"Admin",existing + "|Grp(" + ecm_domain_admin_oid_group_cn + ")")
print("WLS Admins (updated) : " +  roleMapper.getRoleExpression(None,"Admin"))

disconnect()
exit()

This should take care of all the infrastructure steps required.  Now it is just a matter of creating a little web application in JDeveloper that leverages RIDC to connect to the UCM Server using JAX/WS protocol and GPA client policy.

File > New > General > Applications > Custom Application
Application Name: RIDCJAXWSTest
Project Name: web
Product Features: JSP and Servlets

Right click on web project > New > Web Tier > Servlets > HTTP Servlet
Web Application Version: Servlet 2.5\JSP 2.1
Servlet class: RIDCTestServlet
Servlet package: web
Mapping details:
Name: RIDCTestServlet
URL Pattern /app

Right click on web project > Project Properties > Libraries and Classpath
Add and select a new user library with classpath referencing oracle.ucm.ridc-11.1.1.jar

Servlet and XML Code

web/RIDCTestServlet.java :-

package web;

import java.io.IOException;
import java.io.PrintWriter;

import java.util.Map;

import javax.servlet.*;
import javax.servlet.http.*;

import oracle.stellent.ridc.IdcClient;
import oracle.stellent.ridc.IdcClientException;
import oracle.stellent.ridc.IdcClientManager;
import oracle.stellent.ridc.IdcContext;

import oracle.stellent.ridc.model.DataBinder;
import oracle.stellent.ridc.model.DataObject;

import oracle.stellent.ridc.protocol.ServiceResponse;

public class RIDCTestServlet extends HttpServlet
{
  private static final String url = "
http://ucmserver.com:16200/idcnativews";
   
  private IdcClient m_idcClient = null;

  public void init(ServletConfig config) throws ServletException
  {
    super.init(config);
    IdcClientManager clientManager = new IdcClientManager();
    try
    {
      m_idcClient = clientManager.createClient(url);
    }
    catch (IdcClientException e)
    {
      e.printStackTrace();
      throw new RuntimeException(e);
    }

    // enable verbose ridc logging to console
    oracle.stellent.ridc.common.log.LogFactory.setLogProvider(
      new oracle.stellent.ridc.common.log.simple.SimpleLogProvider());
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
  {
    doPost(request, response);
  }

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
  {
    PrintWriter out = response.getWriter();
    out.print("<html><head><title>RIDC Test Servlet</title></head><body><pre>");
   
    try
    {
   
      out.print("RIDC Version: " + m_idcClient.getVersion() + "<br/><hr/>");
     
      String username = request.getRemoteUser();
      out.print("request.getRemoteUser() = " + username + "<br/><hr/>");
     
      // we are using SAML
      IdcContext userContext = userContext = new IdcContext(username);

      // get the binder
      DataBinder binder = m_idcClient.createBinder();
 
      // populate the binder with the parameters
      binder.putLocal("IdcService", "PING_SERVER");

      out.print("Sending PING_SERVER request ...<br/>");

      // execute the request
      ServiceResponse resp = m_idcClient.sendRequest(userContext, binder);
 
      // get the binder - get a binder closes the response automatically
      DataBinder responseBinder = resp.getResponseAsBinder();
     
      out.print("<hr/>PING_SERVER response ...<br/>");

      DataObject localData = responseBinder.getLocalData();
      for (Map.Entry<String, String> entry : localData.entrySet())
      {
        out.print(entry.getKey() + "=" + entry.getValue() + "<br/>");
      }
     
      m_idcClient.logout(userContext); // only new versions of RIDC support this
    }
    catch (Exception e)
    {
      out.print("Exception: <br/><hr/>");
      e.printStackTrace(out);
    }
    finally
    {
      out.print("</pre></body></html>");
    }
  }
}

WEB-INF/web.xml :-

<?xml version = '1.0' encoding = 'UTF-8'?>
<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_2_5.xsd"
         version="2.5">
  <servlet>
    <servlet-name>RIDCTestServlet</servlet-name>
    <servlet-class>web.RIDCTestServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>RIDCTestServlet</servlet-name>
    <url-pattern>/app</url-pattern>
  </servlet-mapping>

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Constraint-0</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>valid-users</role-name>
    </auth-constraint>
  </security-constraint>

  <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>myrealm</realm-name>
  </login-config>

  <security-role>
    <role-name>valid-users</role-name>
  </security-role>
</web-app>

 

WEB-INF/weblogic.xml :-

<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app xmlns:wls="
http://xmlns.oracle.com/weblogic/weblogic-web-app" 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_2_5.xsd http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.0/weblogic-web-app.xsd">

  <wls:security-role-assignment>
    <wls:role-name>valid-users</wls:role-name>
    <wls:principal-name>users</wls:principal-name>
  </wls:security-role-assignment>

  <!--
  the users group is automatically made available to the security realm;
  this group represents the set of all authenticated users.
  Any user that successfully authenticates itself is a member of the users group.
  -->
</wls:weblogic-web-app>

 

Sample Output:  (after authenticating as user ecmadmin)

RIDC Version: 11.1.1.6.3.7848

request.getRemoteUser() = ecmadmin

Sending PING_SERVER request ...

PING_SERVER response ...
UserDateFormat=iso8601
UserTimeZone=UTC
ClientEncoding=UTF-8
IdcService=PING_SERVER
dUser=ecmadmin
refreshSubjects=
blDateFormat=yyyy-MM-dd HH:mm:ssZ!tUTC!mAM,PM
ECID-Context=1.8987b47a9955d4f6:328ee95f:1382badfe3d:-8000-0000000000000039;kXjE
refreshMonikers=
changedSubjects=
refreshSubMonikers=
blFieldTypes=xForceFolderSecurity text,xPartitionId text,dInDate date,xReadOnly text,xPartnerLevel bigtext,dMessage message,xInhibitUpdate text,xPartnerProgram bigtext,dCreateDate date,xWebFlag text,xHidden text,xPartnerType bigtext,dReleaseDate date,StatusMessage message,xCollectionID int,xStorageRule text,dOutDate date,xExternalDataSet bigtext,xComments memo,xIdcProfile text
NoHttpHeaders=0
changedMonikers=
idcToken=
StatusMessage=You are logged in as 'ecmadmin'.
hasUserAccessChanged=1
IsJava=1
localizedForResponse=1

Spaces wrongly added in OCR text of PDF generated by Abbyy FineReader - Solution

In my search for the holy grail of document scanning and management software, I’ve been trialing Abbyy FineReader 11 and Adobe Acrobat X to see if either offer any benefits over Lucion FileCenter on the scanning front.  FileCenter is a nice piece of software for managing of scans, but it falls short in my opinion on the OCR front. 

What I’m hoping to find in a product is something that offers a high level of OCR accuracy, and the ability to easily correct mistakes with a quick and simple interface (preferably all controlled by keyboard commands for improved processing time).

I’m after a product that could learn / be trained to detect document types and only OCR the data of actual interest.

I’m after a product that easily and automatically recognize document paper size and allows simple and effective trim/deskew/cleanup etc as necessary.

Anyway, these requirements culminated in my testing of FineReader and Acrobat X.

Acrobat X has the potential to be a useful piece of software, but it surprisingly performed quite average on the OCR front with the documents that I threw at it.  The OCR results were average – and that is being kind.  What astonished me however, was the product’s inability to provide a simple mechanism to correct the OCR mistakes.  I had to google search for a way to achieve it; and if anything, the results returned were more a workaround/hack than out-of-the-box intended functionality.  What it did do nicely was the simple one click scan to PDF.  It was just a pity the OCR let it down.

Next up was FineReader 11.  This software appeared to excel on the OCR front, and also allow a simple verification mechanism to correct issues detected.  It however was not without its warts.   Features that could turn this very good software in to excellent software:

  1. The ability to perform all document verification/correction steps completely using keyboard with absolutely no mouse interaction required.
  2. The ability to save scanning profiles for specific document types.  For example what type of fonts to expect, what paper size, what spelling mistakes/words to ignore.
  3. The ability for spell checker / verifier to ignore single letters that appear as part of a word grouping.  For example,  John Smith is fine.  But it sees an issue with the “J” in J Smith.  I should be able to add “J Smith” to my dictionary ignore list.
  4. The ability to completely skip OCR on specific pages of a document without silly workarounds.  Currently, you have to add a scan region on the page, and read the page.
  5. The ability to better recognize font families and the like.

One major flaw I found in the software, fortunately appears to have a cure. I was hoping others would have run in to it and provided a solution in some type of forum, but alas that was not the case.  Abbyy – if you are reading this post by any chance, I would strongly suggest you consider making a public support forum for people to discuss your software and offer tips etc.  It should definitely lead to more sales if people can find solutions to problems they encounter with your software.

Anyway, the issue I ran in to was the following:

When converting scan to PDF, words had invalid spacing/padding added not present in either the OCR text in Abbyy, or for that matter, in other output options such as HTML etc.

The cause:

The Windows machine likely does not have the matching font installed, and Abbyy likely not configured to leverage the font.

The solution:

First attempt to identify the font leveraged by the document.  If only a scan is available, extract some words containing sufficient sample characters and head on over to http://www.myfonts.com/WhatTheFont/ and attempt to determine the font.

If you can get hold of an electronic PDF of the same file (or similar from same provider), you should be able to go to Acrobat > File > Properties dialog > Fonts tab.  If an electronic version of the document was available, within Acrobat you will likely see that the font has been embedded, but only
a portion of the font was embedded (such that the characters leveraged by the document, and nothing else).

Using tools such as FontForge and mupdf-1.0-tools-windows.zip, you can potentially extract the embedded fonts subsets, and try and convert these to TTF.  But it is likely the TTF won't be sufficient.  Your best bet is to try and find the original TTF on the net.  You may have to purchase it.

One other issue, is that even if you can find the TTF, chances are licensing flags on the TTF will prevent it from being embedded in a PDF document.
To circumvent this, you can leverage ttfpatch and set the fsType flag to 0, meaning: Installable embedding allowed, fonts may be embedded in documents and permanently installed on the remote system.

Install the font then in the <windows>\fonts directory, and open Abbyy.
From Tools > Options > Read tab > Fonts > ensure the font is selected (and thus available for OCR).

If saving the file as PDF/A (which embeds fonts), and an error message appears stating the font is restricted from being embeeded, first ensure the fsType flag has been reset to 0, and also try and clear out Abbyy font cache.  Delete directory
C:\Documents and Settings\All Users\Application Data\ABBYY\FineReader\11.00\FontCache

Also, it is worthwhile installing Microsoft Typography Font Properties Extension
http://www.microsoft.com/typography/FreeToolsOverview.mspx
This will allow you to right click > properties on a TTF file, and see the Embedding restrictions on the file.

Good luck!

Friday, February 17, 2012

Disable Acrobat X Welcome Screen

Create and invoke the following registry file to disable the annoying welcome screen in Acrobat X Pro on Windows:

Disable_Acrobat_X_Pro_Welcome_Screen.reg contents:

REGEDIT4

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Adobe\Adobe Acrobat\10.0\FeatureLockDown\cWelcomeScreen]
"bShowWelcomeScreen"=dword:00000000

Tuesday, April 19, 2011

dircolors – color ls unix directory listings – danger of overriding shell built-ins

I ran in to a crazy issue today that stemmed from a built-in function override configuration setting that I have leveraged for the best part of a decade. I’m amazed that this is the first time I actually noticed it!

I have a environment setup script for one of my installations like the following ...

export MW_HOME=/u01/app/oracle/product/Middleware
export JAVA_HOME="`ls -d ${MW_HOME}/jdk16*`"
export PATH=$JAVA_HOME/bin:$PATH

The JDK path is not hardcoded as this is a pre-release environment, and the JDK is often patched and the directory name changes as a result.

A script I was invoking had a simple test like the following:

#!/bin/sh
if [ x${PATH} != x ]; then
    PATH=$ORACLE_HOME/bin:$PATH
fi

When I ran the shell script from plain vt100 terminal, everything worked perfectly.  When I fired the script up from an xterm running inside VNC, I hit an error like the following:

./test.sh: line 2: [: too many arguments

The reason for the error, the string JAVA_HOME ended up containing ANSI characters when run from xterm.

For example, from an XTERM

echo `ls -d /tmp` > file.txt

file.txt contained ... ^[[00m^[[00;34m/tmp^[[00m ^[[m

, whereas from the VT100 terminal:

echo `ls -d /tmp` > file.txt

file.txt contained ... /tmp

It turns out, I had a zsh function setup-for/overriding the ls command so as to use color listings when invoked from an xterm by passing the --color option.  The function was as follows:

ls ()
{
  [[ $TERM = (*xterm*|ansi|linux|dtterm) ]] && set -- --color "$@"
  command ls -v "$@"
}

The fix was to change the --color option to be --color=auto so that the ANSI escape sequences were only output if the standard output is a terminal (but not for things like scripts etc).

ls ()
{
  [[ $TERM = (*xterm*|ansi|linux|dtterm) ]] && set -- --color=auto "$@"
  command ls -v "$@"
}

Moral of the story, be extremely careful using shell functions to override standard built-in commands, and make sure you understand the impact of switches!

Update

So my good mate and colleague Dragos states:

You should not rely on 'ls' (or any other command), as it can be an alias or function to anything else.

The fix is in your environment script to use /bin/ls or =ls, yes "equal sign ls"

There's a way to force filename expansion too:

JAVA_HOME=(${MW_HOME}/jdk16*)

Thursday, April 7, 2011

Online Open Port Check Tool

The RCCB / Residual Current Device in my place has a nasty habit of tripping whenever the house is vacant for more than a couple of days. For the remaining 99% of the time that I’m home, it never trips.  I haven’t been able to isolate exactly what triggers it, but upon my return I’m faced with a fridge/freezer full of ruined food.

Anyway, given the electricity is completely lost when this RCCB triggers, I really need a simple tool that polls my router (like a ping) to see if it responds, and if not, gives me some type of warning.

I run OpenVPN on the router over TCP port 443, and it appears the various online port scanning tools are able to detect the presence of the port being open.

The following is a list of sites that I’ve found that allow one to scan a specific remote host and port (that differs to the host/proxy of the incoming client):

http://ping.eu/port-chk/
http://www.subnetonline.com/pages/network-tools/online-port-scanner.php
http://www.digitalcoding.com/tools/open-port-check.html
http://www.yougetsignal.com/tools/open-ports/

My favourite is the latter, as it allows you to potentially bookmark the location; for example, to check if google port 80 open:

http://www.yougetsignal.com/tools/open-ports/php/check-port.php?portNumber=80&remoteAddress=www.google.com

If anyone knows a site out there that offers remote port polling with SMS/email notification in the event the service is down, let me know!

I may have to build something otherwise on Google App Engine using the URL fetch capabilities

Here is the API   http://code.google.com/appengine/docs/java/urlfetch/overview.html

Tuesday, March 29, 2011

Tools to locate class file in JAR / CLASSPATH

There are a number of different approaches for obtaining the location (jar/directory) of a class at runtime.  The following approach work pretty well…

Class c = ...
System.out.println(c.getProtectionDomain().getCodeSource().getLocation().toString());

You could also try VM debug options at startup such as:

-XX:-TraceClassLoading
Trace loading of classes.

-XX:-TraceClassLoadingPreorder
Trace all classes loaded in order referenced (not loaded).

In terms of static/offline location of classes, most people will suggest jarbrowser, jarminator, or brute force by leveraging a combination of the unix find command, unzip –l, and grep.

I’ve created a derivative of the offline approach.  Essentially I have three scripts:

  1. jarclasspath is a shell script/java class combo used to formulate a valid and complete list of libraries and directories that are referenced from the specified source JAR by way of META-INF/MANIFEST.MF Class-Path directives. A classpath containing a single JAR file could ultimately expand to hundreds of libraries, should that top-level archive specify Class-Path directives, and in-turn the dependent libraries provide Class-Path directives and so-on.  See this post for more details regarding manifest classpath attributes.
  2. jarcheck is a simple perl script that checks for presence of the specified class file in the specified JAR file/or directory. Note that Class-Path manifest directives can reference JAR files and/or directories. This tool supports searching both.
  3. jarwhich is a demonstration shell script that shows how to leverage the jarclasspath and jarcheck scripts above.  This particular script will search the CLASSPATH (and dependent libraries/directories based on Class-Path directives) to locate the requested class file.

Testing…

Using my UCM flavoured WebLogic Server as the basis for the test, I sourced $DOMAIN_HOME/bin/setDomainEnv.sh to set CLASSPATH.

This resulted in a CLASSPATH containing just 16 entries, however, all is not as it seems! :

% echo $CLASSPATH
/u01/app/oracle/product/Middleware/oracle_common/modules/oracle.jdbc_11.1.1/ojdbc6dms.jar: ...
/u01/app/oracle/product/Middleware/wlserver_10.3/server/lib/weblogic.jar: ...
/u01/app/oracle/product/Middleware/modules/features/weblogic.server.modules_10.3.4.0.jar:
...

% echo $CLASSPATH | tr ":" "\n" | wc -l
16

The weblogic.server.modules_10.3.4.0.jar referenced above in the CLASSPATH however has a Class-Path manifest directive referencing some 180 additional dependent JAR files:

% echo $CLASSPATH | tr ":" "\n" | grep weblogic.server.modules 
/u01/app/oracle/product/Middleware/modules/features/weblogic.server.modules_10.3.4.0.jar

% unzip -l /u01/app/oracle/product/Middleware/modules/features/weblogic.server.modules_10.3.4.0.jar
Archive:  weblogic.server.modules_10.3.4.0.jar
  Length     Date   Time    Name
--------    ----   ----    ----
     8880  03-23-11 05:00   META-INF/MANIFEST.MF
--------                   -------
     8880                   1 file

% unzip -d /tmp /u01/app/oracle/product/Middleware/modules/features/weblogic.server.modules_10.3.4.0.jar META-INF/MANIFEST.MF
Archive:  weblogic.server.modules_10.3.4.0.jar
  inflating: /tmp/META-INF/MANIFEST.MF 

% cat /tmp/META-INF/MANIFEST.MF|more
Manifest-Version: 1.0
Implementation-Vendor: BEA Systems
Implementation-Title: Oracle WebLogic Server Module Dependencies 10.3
Thu Oct 28 06:03:12 PDT 2010
Implementation-Version: 10.3.4.0
Feature-Name: weblogic.server.modules
Source-Repository-Change-Id: 1374366
Class-Path: weblogic.server.modules.wlsve_10.3.4.0.jar ../com.bea.core
.antlr.runtime_2.7.7.jar ../com.bea.core.descriptor.j2ee_1.5.0.0.jar
../com.bea.core.descriptor.j2ee.binding_1.5.0.0.jar ../com.bea.core.d
escriptor.wl_1.3.3.0.jar ../com.bea.core.descriptor.wl.binding_1.3.3.
0.jar ../com.bea.core.datasource6_1.9.0.0.jar ../com.bea.core.datasou
rce6.binding_1.9.0.0.jar ../com.bea.core.beangen_1.7.0.0.jar ../com.b
ea.core.descriptor.settable.binding_1.7.0.0.jar ../com.bea.core.diagn
ostics.accessor_1.5.0.0.jar ../com.bea.core.diagnostics.accessor.bind
ing_1.5.0.0.jar ../com.bea.core.management.core_2.8.0.0.jar ../com.be
a.core.management.core.binding_2.8.0.0.jar ../com.bea.core.ejbgen_1.1
.0.2.jar ../org.apache.ant_1.7.1/lib/ant-all.jar ../com.bea.core.repa
ckaged.apache.bcel_5.2.1.0.jar ../com.bea.core.repackaged.jdt_3.5.2.0
.jar ../com.bea.core.apache.commons.collections_3.2.0.jar ../com.bea.
core.apache.commons.lang_2.1.0.jar ../com.bea.core.apache.commons.poo
l_1.3.0.jar ../com.bea.core.apache.commons.io_1.0.0.0_1-4.jar ../com.
bea.core.apache.commons.fileupload_1.0.0.0_1-2-1.jar ../com.bea.core.
apache.dom_1.0.0.0.jar ../com.bea.core.apache.logging_1.0.0.0.jar ../
org.apache.openjpa_1.2.0.0_1-1-1-SNAPSHOT.jar ../com.bea.core.xml.xml
beans_2.1.0.0_2-5-1.jar ../com.bea.core.logging_1.8.0.0.jar ../com.be
a.core.bea.opensaml_1.0.0.0_6-1-0-0.jar ../com.bea.core.bea.opensaml2
_1.0.0.0_6-1-0-0.jar ../com.bea.core.monitoring.harvester.api_2.3.0.0
.jar ../com.bea.core.monitoring.harvester.jmx_2.3.0.0.jar ../com.bea.
.....

As you can see above, this short 16 entry CLASSPATH soon expands to something massive.  In fact, it expanded to 412 unique entries (primarily JAR files, but also directories)!

Let us run some tests:

% ~/bin/jarwhich oracle.jdbc.driver.OracleDriver
/u01/app/oracle/product/Middleware/oracle_common/modules/oracle.jdbc_11.1.1/ojdbc6dms.jar: oracle/jdbc/driver/OracleDriver.class
##Match Found.
/u01/app/oracle/product/Middleware/wlserver_10.3/server/lib/ojdbc6.jar: oracle/jdbc/driver/OracleDriver.class
##Match Found.


% ~/bin/jarwhich javax.mail.internet.MimeUtility
/u01/app/oracle/product/Middleware/modules/javax.mail_1.1.0.0_1-4-1.jar: javax/mail/internet/MimeUtility.class
##Match Found.

 

And finally, here are the scripts:

jarclasspath

#!/bin/sh
#
# Copyright (c) 2011, Matt Shannon.
#
#    NAME      jarclasspath
#

if [ -z "${JAVA_HOME}" ]; then
  printf "\n\nError: Ensure JAVA_HOME environment variable is set.\n"
  exit 1
fi

# Checks error
checkerror()
{
  RESULTCODE=$?
  if [ ${RESULTCODE} -ne 0 ];then
    exit 1
  fi
}

JAVARUNCMD="${JAVA_HOME}/bin/java"
JAVACMPCMD="${JAVA_HOME}/bin/javac"

## note - need to be careful with dollar signs and backslashes!

cat > /tmp/JarClasspath.java <<EOF
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;

import java.util.List;
import java.util.ArrayList;

import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;


public class JarClasspath
{
  public static void main(String[] args)
  {
    if (args.length < 0)
    {
      System.err.println("Usage: arg0 = /path/to/jar");
      System.exit(-1);
    }

    String sourceJar = args[0];

    List<String> jars = new ArrayList<String>();

    try
    {
      File entry = new File(sourceJar);
      String entryPath = entry.getCanonicalPath();

      if (entry.isDirectory())
      {
        System.out.println(entryPath);
      }
      else if (entry.isFile())
      {
        if ((entryPath.toLowerCase().endsWith(".jar") ||
          entryPath.toLowerCase().endsWith(".zip")))
        {
          System.out.println(entryPath);
          jars.add(entryPath);
          printManifestClasspathEntries(jars, entry);
        }
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
      System.exit(-1);
    }
  }

  public static void printManifestClasspathEntries(
    List<String> jars,
    File sourceJar
  ) throws Exception
  {
    File[] entries = getManifestClasspathEntries(sourceJar);
    int length = (entries == null) ? 0 : entries.length;
    for (int i = 0 ; i < length; i++)
    {
      File entry = entries[i];
      if (entry == null)
      {
        continue;
      }

      String entryPath = entry.getCanonicalPath();

      if (entry.isDirectory())
      {
        System.out.println(entryPath);
      }
      else if (entry.isFile())
      {
        if ((entryPath.toLowerCase().endsWith(".jar") || 
          entryPath.toLowerCase().endsWith(".zip")))
        {
          if (! jars.contains(entryPath))
          {
            jars.add(entryPath);
            System.out.println(entryPath);
            printManifestClasspathEntries(jars, entry);
          }
        }
      }
    }
  }


  public static File[] getManifestClasspathEntries(File file)
    throws IOException, FileNotFoundException
  {
    File[] results = null;

    InputStream is = null;
    try
    {
      is = new FileInputStream(file);

      JarFile jarfile = new JarFile(file);

      Manifest manifest = jarfile.getManifest();
      if (manifest != null)
      {
        Attributes attributes = manifest.getMainAttributes();
        if (attributes != null)
        {
          String cp = attributes.getValue(Attributes.Name.CLASS_PATH);
          if (cp != null)
          {
            String[] entries = cp.split("\\\\s");
            int length = (entries == null) ? 0 : entries.length;
            if (length > 0)
            {
              results = new File[length];
              for (int i = 0 ; i < length; i++)
              {
                if ((entries[i] == null) || (entries[i].trim().length() == 0))
                {
                  continue;
                }
                File f = new File(file.getParentFile(), entries[i]);
                results[i] = f;
              }
            }
          }
        }
      }
    }
    finally
    {
      if (is != null)
      {
        try
        {
          is.close();
        }
        catch (Exception ignore)
        {
          ;
        }
        is = null;
      }
    }
    return results;
  }
}
EOF

CP=/tmp

# Compile tool
${JAVACMPCMD} -classpath ${CP} -d "/tmp" /tmp/JarClasspath.java
checkerror

# Process arguments
for jar in "$@"; do
  ${JAVARUNCMD} -classpath ${CP} JarClasspath "${jar}"
  checkerror
done

jarcheck

#!/usr/bin/perl -w
#
# Checks if specified class is found in provided jar/directory
#
use Getopt::Std;

$0 =~ /([^\/]+)$/ ; # Pattern match; 1 or more chars at end of perl script file name (after the last / if present)
$SCRIPT = $1 ; # Contains the subpattern from the first set of parentheses in the last pattern matched

# Get the class argument.
getopts('s');

unless (((scalar @ARGV) == 2) &&
(defined ($class = $ARGV[0])) && ($class =~ /^[\w\.\/]+$/) &&
(defined ($jar = $ARGV[1])) && ($jar =~ /^[\w\.\/\-]+$/))
{
  die("Usage: $SCRIPT [-s] <class> <jar>\nwhere:",
      "\t<class> is the fully-qualified, dot or slash delimited name of a Java class.\n",
      "\t<jar> is the fully-qualified jar file or directory to search.\n",
      "\t-s tells jarcheck that the full class name is not provided\n");
}

# Get the partial name of the .class file for this class.
$classFile = $class ;
$classFile =~ s/\./\//g ;
$classFile .= ".class" ;

# If the jar is a file, Search it for the class file name.
if ((-f $jar) && (open(ARCHIVE, "unzip -l $jar|")))
{
  while(defined ($line = <ARCHIVE>))
  {
# escape any $ in the class file name
    $escaped = $classFile;
    $escaped =~ s/\$/\\\$/;

    # \b allows you to perform a "whole words only" search using a regular expression
if($line =~ /\b$escaped\b/)
    {
   print("$jar: ");
   if (defined($opt_s))
   {
     $line =~ /[_\w\/\$]+$classFile/;
     print($&);
   }
   else
   {
     print($classFile);
   }
   print("\n");

   exit(0);
}
  }
}
# If the jar is infact a directory, see if the .class file is under it.
elsif(-d $jar)
{
  if (-f "$jar/$classFile")
  {
print("$jar/$classFile\n");
exit(0);
  }
  elsif (defined($opt_s))
  {
$classFile =~ /[_\w]+\.class/;
$maxdepth = ($jar eq ".")? "-maxdepth 1":"";
$results = `find $jar $maxdepth | grep -w $&`;
if (!$?)
{
   print $results;
   exit(0);
}
  }
}

exit(1);

jarwhich

#!/bin/sh
#
# Copyright (c) 2011, Matt Shannon.
#
#    NAME      jarwhich
#

if [[ $# -ne 1 ]]; then
  printf "\nUsage: $0 <class>\n"
  printf "  where <class> is the fully-qualified, dot or slash delimited name of\n"
  printf "  the Java class to locate. e.g. oracle.jdbc.driver.OracleDriver\n"
  exit
fi

if [ -z "${CLASSPATH}" ]; then
  printf "\nError: Ensure CLASSPATH environment variable is set.\n"
  exit 1
fi

DIRNAMECMD=`which dirname`
SCRIPT_DIR="`${DIRNAMECMD} $0`/"
SCRIPT_DIR="`cd \"${SCRIPT_DIR}\" && pwd`"


jarCheck()
{
  "${SCRIPT_DIR}"/jarcheck "$1" "$2"
  return
}

file=/tmp/out$$
echo $CLASSPATH | tr ":" "\n" | sort -u | xargs "${SCRIPT_DIR}"/jarclasspath | sort -u > $file

for line in `cat $file`; do
  jarCheck $1 "${line}"
  if [[ $? -eq 0 ]]; then
    printf "##Match Found.\n"
    # exit 0
  fi
done