@@ -192,6 +192,7 @@ LmResult LmRoutineJavaObj::invokeRoutineMethod(
           (*emitRowPtr_)(NULL,0,&qs);
         }
     }
+  jni->DeleteLocalRef(jniResult);
 
   if (invocationInfoLenOut)
     *invocationInfoLenOut = static_cast<Int32>(iiLen_);
// @@@ START COPYRIGHT @@@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
//
// @@@ END COPYRIGHT @@@

#include "LmRoutineJavaObj.h"
#include "org_trafodion_sql_udr_UDR.h"

LmResult LmRoutineJavaObj::setRuntimeInfo(
     const char   *qid,
     int           totalNumInstances,
     int           myInstanceNum,
     ComDiagsArea *da)
{
  LmResult result = LM_OK;
  JNIEnv *jni = (JNIEnv*)getLM()->jniEnv_;
  jstring jqid = jni->NewString((const jchar *) qid, strlen(qid));

  // call the Java method that will ultimately call the user code
  jni->CallVoidMethod(
       udrInvokeObjectG_,
       (jmethodID) getLM()->setRuntimeInfoId_,
       jqid,
       (jint) totalNumInstances,
       (jint) myInstanceNum);

  if (jni->ExceptionOccurred())
    {
      getLM()->exceptionReporter_->checkJVMException(da, 0);
      if (da)
        {
          // an error without a diagnostic message from Java
          *da << DgSqlCode(-LME_OBJECT_INTERFACE_ERROR)
              << DgString0(getNameForDiags())
              << DgString1("runtime")
              << DgString2("LmRoutineJavaObj::setRuntimeInfo()")
              << DgString3("Exception in JNI call");
        }
      result = LM_ERR;
    }

  // delete input parameter objects
  jni->DeleteLocalRef(jqid);

  return result;
}

LmResult LmRoutineJavaObj::invokeRoutineMethod(
     /* IN */     tmudr::UDRInvocationInfo::CallPhase phase,
     /* IN */     const char   *serializedInvocationInfo,
     /* IN */     Int32         invocationInfoLen,
     /* OUT */    Int32        *invocationInfoLenOut,
     /* IN */     const char   *serializedPlanInfo,
     /* IN */     Int32         planInfoLen,
     /* IN */     Int32         planNum,
     /* OUT */    Int32        *planInfoLenOut,
     /* IN */     char         *inputRow,
     /* IN */     Int32         inputRowLen,
     /* OUT */    char         *outputRow,
     /* IN */     Int32         outputRowLen,
     /* IN/OUT */ ComDiagsArea *da)
{
  LmResult result = LM_OK;
  JNIEnv *jni = (JNIEnv*)getLM()->jniEnv_;
  if (inputRowLen >= 0)
    inputRowLen_ = inputRowLen;
  if (outputRowLen >= 0)
    outputRowLen_ = outputRowLen;

  jbyteArray jii = jni->NewByteArray((jint) invocationInfoLen);
  jbyteArray jpi = jni->NewByteArray((jint) planInfoLen);
  jbyteArray jir = jni->NewByteArray((jint) inputRowLen_);

  jni->SetByteArrayRegion(jii,
                          (jsize) 0,
                          (jsize) invocationInfoLen,
                          (const jbyte *) serializedInvocationInfo);
  jni->SetByteArrayRegion(jpi,
                          (jsize) 0,
                          (jsize) planInfoLen,
                          (const jbyte *) serializedPlanInfo);
  jni->SetByteArrayRegion(jir,
                          (jsize) 0,
                          (jsize) inputRowLen_,
                          (const jbyte *) inputRow);

  // call the Java method that will ultimately call the user code
  jobject jniResult = jni->CallObjectMethod(
       udrInvokeObjectG_,
       (jmethodID) getLM()->routineMethodInvokeId_,
       (jint) phase,
       jii,
       jpi,
       (jint) planNum,
       jir);

  if (jniResult == NULL || jni->ExceptionOccurred())
    {
      // this catches any exceptions other than UDRException
      // thrown in the Java code
      getLM()->exceptionReporter_->checkJVMException(da, 0);
      if (da && da->mainSQLCODE() >= 0)
        {
          // an error without a diagnostic message from Java
          char buf[4];
          snprintf(buf, sizeof(buf), "%d", static_cast<int>(phase));
          *da << DgSqlCode(-LME_OBJECT_INTERFACE_ERROR)
              << DgString0(getNameForDiags())
              << DgString1(buf)
              << DgString2("LmRoutineJavaObj::invokeRoutineMethod()")
              << DgString3("JNI call returned a NULL object");
        }
      result = LM_ERR;
    }

  // delete input parameter objects
  jni->DeleteLocalRef(jii);
  jni->DeleteLocalRef(jpi);
  jni->DeleteLocalRef(jir);

  // clear return info from any previous calls
  if (returnedIIL_)
    {
      jni->DeleteLocalRef(returnedIIL_);
      returnedIIL_ = NULL;
    }
  if (returnedPIL_)
    {
      jni->DeleteLocalRef(returnedPIL_);
      returnedPIL_ = NULL;
    }

  iiLen_ = piLen_ = 0;

  if (result == LM_OK)
    {
      // retrieve returned information
      jint returnStatus = jni->GetIntField(
           jniResult, 
           (jfieldID) getLM()->returnInfoStatusField_);

      if (returnStatus != 0)
        {
          getLM()->exceptionReporter_->processJavaObjException(
               jniResult,
               returnStatus,
               static_cast<int>(phase),
               "LmRoutineJavaObj::invokeRoutineMethod()",
               getNameForDiags(),
               da);
          result = LM_ERR;
        }
      else
        {
          returnedIIL_ = static_cast<jbyteArray>(
               jni->GetObjectField(
                    jniResult,
                    (jfieldID) getLM()->returnInfoRIIField_));
          returnedPIL_ = static_cast<jbyteArray>(
               jni->GetObjectField(
                    jniResult,
                    (jfieldID) getLM()->returnInfoRPIField_));

          if (returnedIIL_)
            iiLen_ = jni->GetArrayLength(returnedIIL_);

          if (returnedPIL_)
            piLen_ = jni->GetArrayLength(returnedPIL_);
        }

      if (phase == tmudr::UDRInvocationInfo::RUNTIME_WORK_CALL &&
          result == LM_OK)
        {
          SQLUDR_Q_STATE qs = SQLUDR_Q_EOD;
          // emit the EOD (end of data) for the result table
          (*emitRowPtr_)(NULL,0,&qs);
        }
    }

  if (invocationInfoLenOut)
    *invocationInfoLenOut = static_cast<Int32>(iiLen_);

  if (planInfoLenOut)
    *planInfoLenOut = static_cast<Int32>(piLen_);

  return result;
}

LmResult LmRoutineJavaObj::getRoutineInvocationInfo(
     /* IN/OUT */ char         *serializedInvocationInfo,
     /* IN */     Int32         invocationInfoMaxLen,
     /* OUT */    Int32        *invocationInfoLenOut,
     /* IN/OUT */ char         *serializedPlanInfo,
     /* IN */     Int32         planInfoMaxLen,
     /* IN */     Int32         planNum,
     /* OUT */    Int32        *planInfoLenOut,
     /* IN/OUT */ ComDiagsArea *da)
{
  JNIEnv *jni = (JNIEnv*)getLM()->jniEnv_;
  LmResult result = LM_OK;

  // return the serialized invocation info
  if (returnedIIL_)
    {
      if (invocationInfoMaxLen < iiLen_ && serializedInvocationInfo)
        {
          if (da)
            *da << DgSqlCode(-LME_OBJECT_INTERFACE_ERROR)
                << DgString0(getNameForDiags())
                << DgString1("unknown")
                << DgString2("getRoutineInvocationInfo()")
                << DgString3("Buffer too short for invocation info");
          iiLen_ = 0;
          result = LM_ERR;
        }
      else if (serializedInvocationInfo)
        {
          jbyte * returnedIIChar = jni->GetByteArrayElements(returnedIIL_, NULL);
          str_cpy_all(serializedInvocationInfo,
                      reinterpret_cast<const char *>(returnedIIChar),
                      iiLen_);
          jni->ReleaseByteArrayElements(returnedIIL_, returnedIIChar, JNI_ABORT);
        }
      jni->DeleteLocalRef(returnedIIL_);
      returnedIIL_ = NULL;
    }

  if (invocationInfoLenOut)
    *invocationInfoLenOut = iiLen_;
  iiLen_ = 0;

  // return the serialized plan info
  if (returnedPIL_)
    {
      if (planInfoMaxLen < piLen_ && serializedPlanInfo)
        {
          if (da)
            *da << DgSqlCode(-LME_OBJECT_INTERFACE_ERROR)
                << DgString0(getNameForDiags())
                << DgString1("unknown")
                << DgString2("getRoutineInvocationInfo()")
                << DgString3("Buffer too short for plan info");
          piLen_ = 0;
          result = LM_ERR;
        }
      else if (serializedPlanInfo)
        {
          jbyte * returnedPIChar = jni->GetByteArrayElements(returnedPIL_, NULL);
          str_cpy_all(serializedPlanInfo,
                      reinterpret_cast<const char *>(returnedPIChar),
                      piLen_);
          jni->ReleaseByteArrayElements(returnedPIL_, returnedPIChar, JNI_ABORT);
        }
      jni->DeleteLocalRef(returnedPIL_);
      returnedPIL_ = NULL;
    }

  if (planInfoLenOut)
    *planInfoLenOut = piLen_;
  piLen_ = 0;

  return result;
}

LmResult LmRoutineJavaObj::setFunctionPtrs(
     SQLUDR_GetNextRow getNextRowPtr,
     SQLUDR_EmitRow emitRowPtr,
     ComDiagsArea *da)
{
  JNIEnv *jni = (JNIEnv*)getLM()->jniEnv_;
  LmResult result = LM_OK;

  // save a local copy to be able to emit the EOD (end of data)
  emitRowPtr_ = emitRowPtr;

  const JNINativeMethod methods[2] = {
    { const_cast<char *>("SpInfoGetNextRowJava"),
      const_cast<char *>("([BILorg/trafodion/sql/udr/UDR$QueueStateInfo;)V"),
      reinterpret_cast<void *>(jniGetNextRowPtr_) },
    { const_cast<char *>("SpInfoEmitRowJava"),
      const_cast<char *>("([BILorg/trafodion/sql/udr/UDR$QueueStateInfo;)V"),
      reinterpret_cast<void *>(jniEmitRowPtr_) }
  };

  // use this method to register the native methods, which are already
  // loaded in the executable, not need to load a separate DLL
  jint returnStatus = jni->RegisterNatives(
       reinterpret_cast<jclass>(getLM()->udrClass_),
       methods,
       static_cast<jint>(sizeof(methods)/sizeof(methods[0])));
       
  if (returnStatus != 0 || jni->ExceptionOccurred())
    {
      getLM()->exceptionReporter_->insertDiags(
           da,
           -LME_INTERNAL_ERROR,
           ": Error in LmRoutineJavaObj::setFunctionPtrs()");

      if (da)
        {
          char buf2[80];
          snprintf(buf2, sizeof(buf2), "Return status %d from JNI call", returnStatus);

          *da << DgSqlCode(-LME_OBJECT_INTERFACE_ERROR)
              << DgString0(getNameForDiags())
              << DgString1("runtime initial")
              << DgString2("LmRoutineJavaObj::setFunctionPtrs()")
              << DgString3(buf2);
        }
      result = LM_ERR;
    }

  return result;
}

void LmRoutineJavaObj::setJNIFunctionPtrs(void *jniGetNextRowPtr,
                                          void *jniEmitRowPtr)
{
  jniGetNextRowPtr_ = jniGetNextRowPtr;
  jniEmitRowPtr_ = jniEmitRowPtr;
}

void LmRoutineJavaObj::setRowLengths(int inputRowLen,
                                     int outputRowLen)
{
  inputRowLen_  = inputRowLen;
  outputRowLen_ = outputRowLen;
}

LmRoutineJavaObj::LmRoutineJavaObj(
     const char   *sqlName,
     const char   *externalName,
     const char   *librarySqlName,
     ComRoutineTransactionAttributes transactionAttrs,
     ComRoutineSQLAccess sqlAccessMode,
     ComRoutineExternalSecurity externalSecurity,
     Int32    routineOwnerId,
     const char   *serializedInvocationInfo,
     int           invocationInfoLen,
     const char   *serializedPlanInfo,
     int           planInfoLen,
     LmLanguageManagerJava *lm,
     jobject       udrObject,
     LmContainer  *container,
     ComDiagsArea *diagsArea) : LmRoutineJava(sqlName,
                                              externalName,
                                              librarySqlName,
                                              0,
                                              NULL,
                                              0,
                                              NULL,
                                              COM_STYLE_JAVA_OBJ,
                                              transactionAttrs,
                                              sqlAccessMode,
                                              externalSecurity,
                                              routineOwnerId,
                                              NULL,
                                              0,
                                              0,
                                              NULL,
                                              NULL,
                                              NULL,
                                              lm,
                                              (LmHandle) NULL,
                                              container,
                                              diagsArea),
                                udrObjectG_(NULL),
                                udrInvokeObjectG_(NULL),
                                returnedIIL_(NULL),
                                returnedPIL_(NULL),
                                iiLen_(0),
                                piLen_(0),
                                inputRowLen_(-1),
                                outputRowLen_(-1),
                                emitRowPtr_(NULL),
                                jniGetNextRowPtr_(NULL),
                                jniEmitRowPtr_(NULL)
{
  JNIEnv *jni = (JNIEnv*)getLM()->jniEnv_;
  jbyteArray jii = jni->NewByteArray((jint) invocationInfoLen);
  jbyteArray jpi = NULL;
  jobject udrInvokeObject = NULL;

  jni->SetByteArrayRegion(jii,
                          (jsize) 0,
                          (jsize) invocationInfoLen,
                          (const jbyte *) serializedInvocationInfo);

  if (planInfoLen > 0)
    {
      jpi = jni->NewByteArray((jint) planInfoLen);

      jni->SetByteArrayRegion(jpi,
                              (jsize) 0,
                              (jsize) planInfoLen,
                              (const jbyte *) serializedPlanInfo);
    }

  if (!jni->ExceptionOccurred())
    // Do a JNI call to allocate an LmUDRObjMethodInvoke object
    udrInvokeObject = jni->CallStaticObjectMethod(
         (jclass)    getLM()->lmObjMethodInvokeClass_,
         (jmethodID) getLM()->makeNewObjId_,
         udrObject,
         jii,
         jpi);

  jni->DeleteLocalRef(jii);
  if (planInfoLen > 0)
    jni->DeleteLocalRef(jpi);

  if (udrInvokeObject != NULL && !jni->ExceptionOccurred())
    {
      // create global references for these objects, they will
      // last as long as the LmRoutineJavaObj
      udrObjectG_ = jni->NewGlobalRef(udrObject);
      jni->DeleteLocalRef(udrObject);
      udrInvokeObjectG_ = jni->NewGlobalRef(udrInvokeObject);
      jni->DeleteLocalRef(udrInvokeObject);
    }

  if (udrObjectG_ == NULL || udrInvokeObjectG_ == NULL || jni->ExceptionOccurred())
    {
      getLM()->exceptionReporter_->insertDiags(
           diagsArea,
           -LME_INTERNAL_ERROR,
           ": Error in LmRoutineJavaObj::LmRoutineJavaObj()");
    }
}

LmRoutineJavaObj::~LmRoutineJavaObj()
{
  JNIEnv *jni = (JNIEnv*)getLM()->jniEnv_;

  if (udrObjectG_ != NULL)
    jni->DeleteGlobalRef(udrObjectG_);
  if (udrInvokeObjectG_ != NULL)
    jni->DeleteGlobalRef(udrInvokeObjectG_);
  if (returnedIIL_)
    jni->DeleteLocalRef(returnedIIL_);
  if (returnedPIL_)
    jni->DeleteLocalRef(returnedPIL_);
}

ComBoolean LmRoutineJavaObj::isValid() const
{
  return udrObjectG_ != NULL && udrInvokeObjectG_ != NULL;
}

It looks like there is a code path where jniResult could be null. Is DeleteLocalRef a no-op in this case?