Sunday, November 25, 2012

Reminiscing the difficulties of predicting the future or how the iPhone 5 made it all come true

A few year back, at the height of the iPod boom, I predicted its swift demise.
I might have titled this said blog post something like Why the iPod must die!

So back then I was predicting the death of single purpose devices such as MP3 players in general and the iPod in particular. Well... I was wrong, but not all the way. The iPod is still around, but sales are declining. As a matter of fact they have been declining every year since my prediction was made in 2008:



Why do I want to warm up old toast you ask? Good question. Another thing I mentioned towards the end of the aforementioned blog post was about, how I believed, Apple could make boatloads of money, not with the iPod itself, but with the control over the connection mechanism, the 30-pin dock.

Most "i" devices Apple introduced up to iPhone 4S have this connection mechanism. A whole supporting universe of accessory makers has emerged that use that 30-pin standard to connect anything from stereo systems to zebra pattern 3d printers (I made this one up, don't Google needlessly!).
However, Apple, like Sun, when it missed the JVM for the trees, missed to monetize the 30-pin connector handsomely. While normally very astute in locking in consumers and partners alike, this was a big miss, indeed, for Apple. Also contributing to this was the ease with which people could reverse engineer the connector.

But fret no more, under the guise of improving user experience, Apple introduced the new "lightning connector" and closed this loophole with the iPhone 5. Though I have not heard any of my friends or co-workers ever state that they had trouble or needed a new way to connect, it is now a fact of live that we will have to buy many adapters or replace existing gadgets.

Now, the life of the 3rd party accessory maker has changed drastically as well. No longer can they use simple analog techniques to reverse engineer this. There is an encryption chip specific to the connector that needs to be dealt with. Why would you need an encryption chip in the docking connector? To control its use of course. IMHO this thing is so complicated that it delayed the launch of the promised lightning to Apple dock adapter.
Thus, for manufacturers the only viable alternative is to check with Apple to see what the terms of licensing lightning technology would be. This, in turn, translates into revenue in the future for Apple, and my prediction made many years ago, finally comes true !

Cheers,
B.

Tuesday, November 20, 2012

CF: ColdFusion with Amazon Load Balancer (ELB) and mysterious line breaks causing "unterminated string literal" exceptions

This seems like a complicated scenario at first until I started thinking about it again.
You use ColdFusion in the Cloud, specifically in the Amazon cloud, you then add a load balancer in front of your servers, then you notice your JavaScript starting to error out in some browsers. You cannot explain it.
Then, you spent countless hours going nuts.

Here is a sample JavaScript and ColdFusion block that would break. The simple block retrieved a name from a ColdFusion function and passed it to JavaScript:


<cfoutput>
  <script type="text/javascript">
    var myName = '#fCallForName()#';
  </script>
</cfoutput>


The above returned this in browser lets say the name was "John" :



  <script type="text/javascript">
    var myName = '
John';
  </script>


This, in turn, throws a "unterminated string literal" exception in JavaScript because of the line break right before the name. Of course, you say, silly you, you probably doing something in CF to return a Newline character combination. Good thought! So I changed the CF side to be as simple as possible. Here is the CF code that returns an empty string; guaranteed!



<cffunction name="fCallForName" returntype="string">
<cfreturn "">
</cffunction>




So nope, empty string still produced new line in the JavaScript output. So a few brain cycles go by and
I get to thinking to check to hit servers directly (bypassing amazon ELB altogether) and see what is returned. The return this time is enlightening, there is a blank string rather than the expected empty string, but no newline.



  <script type="text/javascript">
    var myName = ' ';
  </script>



Yeah, progress, maybe? This is different, which again it should not be. Thus, the only thing this establishes is that the Amazon load balancer (ELB) is changing the output stream somehow. Then, I also remembered an earlier blog post of mine where I outlined that CF inserts an empty space character when the function output is used directly in place in HTML context:  http://boncode.blogspot.com/2009/03/cf-coldfusion-functions-and-case-of.html
... and the fog began to lift.

I immediately reassigned the function output to a variable like so:

<cfset userName = fCallForName()>

Then, used the variable to output the content of the function:



<cfset userName = fCallForName()>
<cfoutput>
  <script type="text/javascript">
    var myName = '#userName#';
  </script>
</cfoutput>



...and bingo, everything started to work again. No more JavaScript errors. However the conclusions here are more scary then the error:

a) ColdFusion does introduce more than just a space character when function output is used in place
b) Amazon elastic load balancer makes modifications (corrections?) to the network stream and changes the output. For each in place output of function call it adds a line break.

Both a) and b) should not happen, but they do ;o(
At least now my pain could be your gain. Cloud pittfals, I guess...simple assumptions like surely the load balancer will not modify my data could throw you off .

Cheers,
B.





Thursday, October 18, 2012

CF: CFCamp 2012 more of everything

CFCamp 2012 is over. It was another whirlwind affair with more people, more speakers, more topics and more sponsors.
Overall a good gathering that had some nuggets to take away and think about.
Thanks everyone who stayed for my late presentation on practical application security.

You can download presentation slides if you want to review them at your leisure.

Cheers,
B.

Friday, October 12, 2012

CF: CFCamp here we go

Another year, another CFCamp.
The ColdFusion faithful trek into the southerly realms of Munich, Germany to learn all about the intricacies of living the code - dream ;o) So I am my way to join the fine folks, raise a Stein, and catch up on all that is newsworthy.
There will be a selection of topics on HTML5 and mobile since this is a pattern that has been steadily gaining ground among CF'ers and is reflected in this conference.
In addition there is a smaller CFAcademy segment with expanded hands on training.
If you are participating in Security section of CFAcademy watch this blog as I will post some course materials shortly.

CFAcademy users please download your exercise materials.
You will need :

  • One of the CFML engines (Adobe, Railo, OpenBD) installed. I will use Railo to work through examples.
  • A database: MySQL
  • Create a data source "book1" using the sql dump.


Cheers,
Bilal

Saturday, September 29, 2012

Sencha: NCDevCon Presentation Materials

Thanks everyone for attending my presentation on Sencha Architect. I hope the Movie Finder project will provide some insight into how to use Architect to build / prototype your own apps.
I am taking a moment to post the materials I used; hope this makes things clear as mud :o)

Presentation Slides: MVC App in Hours (pdf)

Step-by-Step: Building the Movie Finder App in Sencha Architect (pdf)

Cheers,
B.

Thursday, September 13, 2012

NCDevCon 2012: Regional Developer Conference with focus on Mobile, Web, and ColdFusion


Ok. Now it has been a cool four years that the Triangle Area ColdFusion User’s Group (TACFUG) is putting on a conference NCDevCon 2012 (September 29./30.) for regional developers (everyone is welcome). The user group members are putting in countless volunteer hours to create a conference for developers that everyone can benefit from.
Though, as every year, there is some effort to focus the presentations. This year the focus areas are Mobile, Web, and ColdFusion, though there is a good selection of general topics as well.

Thus, the conference continues to manage to have broad coverage of many relevant areas of ColdFusion, Web and Mobile development while also giving beginners options for hands on sessions.

All this is available for a small fee ($200) compared to $800 to $1000 of dollars we commonly pay. So this is definitely a deal in light of the knowledge that is being shared.

I have been selected to do a presentation on the Mobile side. This year’s topic is “Design MVC Mobile App Visually In Hours”. I am doing a walk-through of the Sencha architect tool as well as some other GUI development for mobile. Lots of demoing so nothing will ever go wrong ;o)

There is still time to book and space available so I hope to connect with everyone there.


Cheers,
-B

Wednesday, August 29, 2012

CF: MXunit Automatically Generating Tests and the Challenge of Test Scope

Let me start by saying that I like unit testing . It is very valuable tool in the arsenal of developers to fight the ever present monsters of recursion bugs and integration nightmares.
However, I will also freely admit that there is no agreement on how many unit tests are ever enough. Put a three developers in a room and you will get three different answers and maybe a headache to boot.

Also, in my case, the other challenge was to determine whether I had sufficient permutations of tests to verify that unit test goals could be met. Mind you that, test-coverage does not equal code coverage, but it probably is a good proxy. So, another goal of mine is to be able to think through all kinds of ways to call on code even the bad stuff and ensure that it behaves as expected. There is system in this, however, and this is what we use come up with most of the tests.

With MXUnit the world of ColdFusion has had a sturdy companion to write all these nice unit tests, however, we needed more. We wanted to have a good number of  tests especially for cfc (ColdFusion components). Coming up with the variety and permutation of tests does require quite a bit of hand coding, so we were looking for an easier way.

The solution we came up with was to generate test stubs. Many, many, many test stubs. We then review the cases and expand the ones we think cover the objective sufficiently. We end up deleting quite a few generated test, but this gives us a good baseline, especially for things we don't commonly unit test but we should, e.g. sending in a number when a string is expected, sending complex values, when simple ones will do and vice versa.

This may not be the way you want to work all test cases but helps to take care of many. We look at a functions parameters and generate all combinations of parameters with standard and break values. This can amount to be many thousandths so use with care.

The generator sample code I am attaching has been squeezed into one code file (cfm template) so it is easier to post. Place the code content into a file named "GenerateTestsPublic.cfm" under your mxunit path. Not optimal but the concepts should be visible. Take it for a spin and make adjustments. Feel free to add comments to the blog post.

Happy Experimenting:

GenerateTestsPublic.cfm:

<!--- 
 Generate mxUnit tests given a component name.
 One File per component function will be generated in this directory.
 
 Will attempt to generate all permutations possible.
 Random values
 Break Values
 
 Prefix:
 Test_[componentName]_[functionName].cfc
 
 An overall TestSuite will be generated
 
 TestSuite_[componentName].cfm
 
 Distributed under Apache 2 Lincese
 (c) 2012 Bilal Soylu 
  --->




<!DOCTYPE HTML>

<html>
<head>
 <title>Generate MXUnit Test for Components</title>
</head>

<body>

<CFIF IsDefined("Form.objectName") AND Trim(Form.objectName) NEQ "">
  
 <!--- get object info  --->
 <cftry>
  <cfset objReg = CreateObject("COMPONENT","#Trim(Form.objectName)#")>
  <cfset stcMeta = getMetaData(objReg)>
 
  
  <!--- generate directory  --->
  
  <!--- generate test files  --->
  <!--- save object name  --->
  <cfscript>
   //save main object
   strName = UCase(Trim(Form.objectName));
   strUseObjectNameInDir = ReplaceNoCase(strName,".","_","ALL");
   strDirPrefix = "unitTests";
   strHint="";
   arrTestCaseNames = [];
   //max test cases (high number of combinations can exist
   intMaxCases = -1;
   intMaxCasesPerFile = 1000;
   if (IsDefined("Form.maxTests") and Val(Form.maxTests) GT 0) intMaxCases = Val(Form.maxTests);
   if (IsDefined("Form.maxTestsPerFile") and Val(Form.maxTestsPerFile) LT intMaxCases) intMaxCasesPerFile = Val(Form.maxTestsPerFile);
   //names & paths
   strBasePath = GetDirectoryFromPath(GetCurrentTemplatePath()) & strDirPrefix & "\";
   strFilePrefix = "Test_#strUseObjectNameInDir#_"; 
   strTestSuiteName = "TestSuite_#strUseObjectNameInDir#.cfm";
   strTestSuitePath = strBasePath & strTestSuiteName;
   strTestCaseDirName = "TestCases_#strUseObjectNameInDir#";
   strTestCaseDirPath = strBasePath & "" & strTestCaseDirName;
   blnComplete=false;
   crlf = chr(13) & chr(10);
   tab = chr(9);
   
   //create directory
   if (NOT DirectoryExists(strTestCaseDirPath)) DirectoryCreate(strTestCaseDirPath);
   
   //sample data
   sampleStructure= ":" & SerializeJSON({"number"=9999,'text'='my text value','dte'=Now()});
   sampleArray= ":" & SerializeJSON(["a","b",33,{"number"=9999,'text'='my text value','dte'=Now()}]);
   sampleQuery = QueryNew("");
   FastFoodArray = ["French Fries","Hot Dogs","Fried Clams","Thick Shakes"];
   nColumnNumber = QueryAddColumn(sampleQuery, "FastFood", "VarChar", FastFoodArray);
   
  
   
  </cfscript>
  
  
  <!--- assemble files  --->
  <cfif IsDefined("stcMeta.Functions") AND ArrayLen(stcMeta.Functions) GT 0>
   <cfloop index="i" from="1" to="#ArrayLen(stcMeta.Functions)#" step="1">
    <!--- each functions has its on test case. init var containers  --->
    <cfset stcFunc = stcMeta.Functions[i]>
    <cfset selParaCombinations = QueryNew("")>
    <cfset stcQueries = {}>
    <cfparam name="stcFunc.Access" default="public">
    <cfif stcFunc.Access IS "public">
     
     <cfset strMethod = UCase(stcFunc.name)>
     <cfset strMethodHint = "">
     <cfset strTestCaseName = strFilePrefix & strMethod>
     <cfset strTestCaseFileName= strTestCaseDirPath & "\#strTestCaseName#.cfc">
     
     <!--- output (bsoylu 03-29-2012) --->
     <cfoutput>
     <br>processing: #strMethod#<br>
     </cfoutput>
     
     <!--- start testCase tC variable  --->
     <cfset genStartTc()>    
     
     <!--- init  --->
     <cfset arrParams = stcFunc.Parameters>     
     
     
     <!--- iterate through each paramter and set base test values  --->
     <cfif (ArrayLen(arrParams) GT 0)>
      <cfset strQList = "">
      <cfloop from="1" to="#ArrayLen(arrParams)#" index="y">
       <cfset stcPara = arrParams[y]>       
       <cfset strParaName= UCase(stcPara.Name)>
       <!--- create query and query handle  --->
       
       <cfset stcQueries["q_#strParaName#"] = QueryNew(strParaName,"CF_SQL_VARCHAR")>
       <cfset selQ = stcQueries["q_#strParaName#"]>
       <cfset strQList = ListAppend(strQList,"q_#strParaName#")>
       
       <!--- if parameter is not required it can be null as a valid state  --->      
       <cfif NOT (IsDefined("stcPara.Required") AND stcPara.Required)>
        <cfset QueryAddRow(selQ)>
        <cfset QuerySetCell(selQ,strParaName,"NULL")>        
       </cfif>
       <!--- by type  --->
       <!--- we prefix with equal sign if we need to eval the data later in processing --->
       <!--- we prefix with colon (:) when we need to deserialize JSON  --->
       <cfif IsDefined("stcPara.Type")>
        <cfif stcPara.Type IS "numeric">
         <!--- for each numeric: use max, use min, use zero, use random  --->
         <!--- Java Long: 9223372036854775807  and -9223372036854775808  --->
         
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"999999999")>         
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"-999999999")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"0")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"#RandRange(0,999999999)#")>         
        </cfif>
        
        <cfif stcPara.Type IS "string">
         <!--- for each string: use max, use empty, use "coldFusion"  --->
         <cfset strLongString = RepeatString("a",4000)>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"#strLongString#")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"coldFusion")>         
        </cfif>
       
        <cfif stcPara.Type IS "struct" OR stcPara.Type IS "any" >
         <!--- for each struct: use empty, use test struct  --->
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,sampleStructure)>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=StructNew()")>         
        </cfif>
        
        <cfif stcPara.Type IS "date" >
         <!--- for each date: use "1/1/1980" use today use tomorrow, use 12/31/2200  --->
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=CreateDate(1980,1,1)")>          
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=Now()")> 
         <cfset tomorrow = DateAdd("d",1,Now())>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=CreateDate(#Year(tomorrow)#,#Month(tomorrow)#,#Day(tomorrow)#)")> 
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=CreateDate(2200,12,31)")>                 
        </cfif>        
       
       
        <cfif stcPara.Type IS "array" >
         <!--- for each array: add sample array and empty  --->
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"=ArrayNew(1)")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,sampleArray)>                                 
        </cfif>  
        
        <cfif stcPara.Type IS "boolean" >
         <!--- for each bool: true/false  --->
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"Yes")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,"No")>                                 
        </cfif> 
        
        <cfif stcPara.Type IS "query" >
         <!--- for each query: sample and empty --->
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,":#SerializeJSON(sampleQuery)#")>
         <cfset QueryAddRow(selQ)>
         <cfset QuerySetCell(selQ,strParaName,":#SerializeJSON(QueryNew(""))#")>                                 
        </cfif>                 
               
       <cfelse>
        <!--- valid state for non-typed or unknown paramters  --->
        <!--- for each undefined type (any): use structure  --->
        <cfset QueryAddRow(selQ)>
        <cfset QuerySetCell(selQ,strParaName,sampleStructure)>       
       
       </cfif>
      
       <!--- for the first parameter query we also set the query result. If there is only one parameter this is what will be returned --->
       <cfif y IS 1>
        <cfset selParaCombinations = selQ>
       </cfif>
      </cfloop><!--- loop through paramters  --->
     
     
      
      <!--- build cartesian product of queries (this will show us all the needed combinations  --->
      <cfset qCounter = 0>
      <cfloop list="#strQList#" index="qName">
       <cfset qCounter ++> 
       <cfif qCounter GT 1>
        <!--- combine into cartesian set. We can only do two queries at a time  --->
        <cfset qToAdd = stcQueries[qName]>
        <cfquery name="selParaCombinations" dbtype="query">
         SELECT DISTINCT *
         FROM selParaCombinations,qToAdd   
        </cfquery>       
       </cfif>  
          
      </cfloop>
      <!--- determine max loop (bsoylu 03-30-2012) --->
      <cfset intMaxLoop = selParaCombinations.RecordCount>
      <cfif intMaxCases GT 0>
       <cfset intMaxLoop = intMaxCases>
      </cfif>
      
      <cfoutput>
       found #selParaCombinations.RecordCount# test combinations 
       <cfif intMaxCases GT 0 AND intMaxCases LT selParaCombinations.RecordCount>
        <b>only #intMaxCases#</b> will be generated      
       </cfif>
       <br>
      </cfoutput>
      <cfflush>
      <!--- loop and generate test for file (bsoylu 03-29-2012) --->
      <cfset intTestCount = 0>
      <cfset intFileCount = 0>
      <cfloop query="selParaCombinations" endrow="#intMaxLoop#"> 
       <cfset tf="">
       <cfset intTestCount ++ >
       <!--- create para structure (bsoylu 03-29-2012) --->
       <cfset stcPara = {}>
       <cfloop list="#selParaCombinations.ColumnList#" index="idxCol">        
        <cfset strVal=Evaluate("selParaCombinations.#idxCol#")>
        <!--- check whether we need to further process the content (bsoylu 03-29-2012) --->
        <cfif strVal neq "NULL">
         <cftry>
          <cfif strVal is "">
           <cfset stcPara[idxCol] = strVal>
          <cfelseif Left(strVal,1) IS ":">
           <cfset stcPara[idxCol] = DeserializeJSON( Right(strVal,Len(strVal)-1))>
          <cfelseif Left(strVal,1) IS "=">
           <cfset stcPara[idxCol] = Evaluate( Right(strVal,Len(strVal)-1))>
          <cfelse>
           <cfset stcPara[idxCol] = strVal>
          </cfif> 
          
          <cfcatch>
           <hr>
           <cfoutput>could not interpret #Right(strVal,Len(strVal)-1)#</cfoutput>
           </hr>
           <cfabort>
          </cfcatch>
         </cftry> 
        </cfif>      
       </cfloop>
      
      
       
       <!--- create function for this para combination (bsoylu 03-29-2012) --->
       <cfset strJSON = SerializeJSON(stcPara)>
       <cfset tf=tf & crlf>     
       <cfset tf=tf & crlf & tab & '<cffunction name="test_#strMethod#_#NumberFormat(selParaCombinations.CurrentRow,"00000")#" >''>
       <cfset tf=tf & crlf & tab & tab & '<cfset var strJSONPara ='''  & strJSON &'''>''>
       <cfset tf=tf & crlf & tab & tab & '<cfset var response ="">''>
       <cfset tf=tf & crlf & tab & tab & '<cfset var argCol = DeserializeJSON(strJSONPara)>''>
       <cfset tf=tf & crlf & tab & tab & '<cfinvoke argumentcollection="##argCol##" component="#strName#" method="#strMethod#" returnvariable="response"/>''>
       
       <cfset tf=tf & crlf & tab & tab & '<cfset assertNotEquals( "",response,SerializeJSON(response) & " - failed call with' & Replace(strJSON,'"','""','ALL') & '")>''>
       <cfset tf=tf & crlf & tab & '</cffunction>''> 
       <cfset tf=tf & crlf>     
      
       <!--- add to test file content (bsoylu 03-29-2012) --->
       <cfset tc=tc & crlf & tf>
       
       <cfif intTestCount mod intMaxCasesPerFile IS 0>
        <!--- write what we have so far (bsoylu 04-03-2012) --->
        <cfset intFileCount++>
        <cfset strModTestCaseName = strTestCaseName & "_File" & intFileCount>
        <cfset tC = tC & crlf & '</cfcomponent>''>
        <!--- set numbered File names (bsoylu 04-03-2012) --->
        <cfset strModTestCaseFileName= strTestCaseDirPath & "\#strModTestCaseName#.cfc">
        <cfset ArrayAppend(arrTestCaseNames,strModTestCaseName)>
        <cffile action="WRITE" file="#strModTestCaseFileName#" output="#tc#" addnewline="No">
        
        <cfoutput>generated sub case file: #strModTestCaseName#<br></cfoutput>
        <!--- reset tc for next file (bsoylu 04-03-2012) --->
        <cfset genStartTc("_File" & intFileCount + 1)>
       </cfif>
       
       
      </cfloop> <!--- set parameters (bsoylu 03-29-2012) --->
      
     </cfif> <!--- we have parameters  --->
     
     <!--- complete the last file (bsoylu 04-03-2012) --->
     <cfset tC = tC & crlf & '</cfcomponent>''>     
     <cfset ArrayAppend(arrTestCaseNames,strTestCaseName)>
     <!--- write test case  --->
     <cffile action="WRITE" file="#strTestCaseFileName#" output="#tc#" addnewline="No">     
     <cfoutput>generated last case file: #strTestCaseName#<br></cfoutput>
    </cfif> <!--- public function  --->

    </cfloop> <!--- loop through functions  --->
   
   <!--- generate test suite file  --->
   <cfset genTestSuite()>
   <cfset blnComplete = true>
  </cfif>
  
  <hr>
 
 
  <cfcatch type="Any">
   <font color="red" size="+2">
   There was an error. Please ensure that your component is located in the correct directory: <BR>
   </font>
   <table cellspacing="2" cellpadding="2" border="1">
    <tr>
     <td><cfdump var="#cfcatch#">    
     </td>
    </tr>    
    </table>
   <HR color="#ff0000" noshade>
  </cfcatch>
 
 </cftry>

 
</CFIF>


<cffunction name="genTestSuite">
 <cfset var tcName ="">
 <cfset tf = crlf> 
 <cfset tf=tf & crlf & '<cfparam name="URL.output" default="extjs">''>
 <cfset tf=tf & crlf & '<cfset testSuitePath = "mxunit.framework.TestSuite" >''>
 <cfset tf=tf & crlf & '<cfset testSuite = createObject("component", testSuitePath).TestSuite() >''>
 
 <cfloop from="1" to="#ArrayLen(arrTestCaseNames)#" index="idxArr"> 
  <cfset tcName = arrTestCaseNames[idxArr]>
  <cfset tf=tf & crlf &   '<cfset uTest = createObject("component", "#strTestCaseDirName#.#tcName#")>''>
  <cfset tf=tf & crlf &   '<cfset testSuite.addAll("#tcName#", uTest) >''>
  <cfset tf=tf & crlf>
 </cfloop>
 
 <cfset tf=tf & crlf>
 <cfset tf=tf & crlf & '<cfset results = testSuite.run() >''>
 <cfset tf=tf & crlf & '<cfset out = results.getResultsOutput(URL.output)>''>
 <cfset tf=tf & crlf & '<cfif NOT IsSimpleValue(out)>''>
 <cfset tf=tf & crlf & tab & '<cfdump var="##out##">''>   
 <cfset tf=tf & crlf & '<cfelse>''>
 <cfset tf=tf & crlf & tab & '<cfoutput>'##out##</cfoutput>'>
 <cfset tf=tf & crlf & '</cfif>''>



 
 <!--- loop through and add tests (bsoylu 03-29-2012) ---> 
 <cffile action="WRITE" output="#tf#" file="#strTestSuitePath#" addnewline="No">

</cffunction>

<cffunction name="genStartTc">
 <cfargument name="strModifier" type="string" default="" hint="modifier for component name">
 
 <!--- start testCase tC  --->
 <cfset tC = '<cfcomponent displayname="MxunitTestCase_#strTestCaseName##Arguments.strModifier#" extends="mxunit.framework.TestCase">''>
 
 <!--- empty call no parameters  --->
 <cfif Arguments.strModifier IS "">
  <cfset tc=tc & crlf & tab & '<cffunction name="testNoParams_#strMethod#" >''>  
  <cfset tc=tc & crlf & tab & tab & '<cfset var response ="">''>  
  <cfset tc=tc & crlf & tab & tab & '<cfinvoke component="#strName#" method="#strMethod#" returnvariable="response"/>''>
  <cfset tc=tc & crlf & tab & tab & '<cfset assertNotEquals( "",response,"No parameter call failed")>''>
  <cfset tc=tc & crlf & tab & '</cffunction>''>
 </cfif>
 
</cffunction>

<cfif IsDefined("blnComplete") and blnComplete>
 <font size="+2" color="#008000">
 <cfoutput>
 Successfully generated Test Suite for Component [#UCase(Form.objectName)#]. <BR>
 Test suite file : <a href="#strDirPrefix#/#strTestSuiteName#" target="_blank" title="run tests">[#strTestSuitePath#]</a> <BR>
 
 Please review and adjust specific test cases.
 </cfoutput>
 </font>
 <cfabort>
</cfif>

<h2>Welcome</h2>






 <BR>
 This program will help you generate mxUnit test cases.
 It requires access to components to be analyzed.
 <BR>
 
 After initial generation you should make changes as the assertion are generic.
 The objective is provide broad test coverage.
 
 <HR>
 Behavior Notes <BR>
 <PRE>
 All files will be generated in the "unitTests" subdirectory based on the initial directory this template is run from
 Will attempt to generate all permutations possible.
 Random values
 Break Values
 
 Test File Name:
 &nbsp;&nbsp;TestCases_[componentName]\Test_[componentName]_[functionName].cfc
 If multiple files are generated per function, a File[n] postfix wil be added except the last one:
 &nbsp;&nbsp;TestCases_[componentName]\Test_[componentName]_[functionName]_File[n].cfc
 
 An overall TestSuite will be generated
 Test Suite name:
 TestSuite_[componentName].cfm
 
 </PRE>
 
 <form method="post" action="GenerateTestsPublic.cfm">
 <table cellspacing="2" cellpadding="2" border="0">

 <tr>
  <td>Component Path from the webroot (e.g. component located [root]/myweb/cfcs/foo.cfc would be myweb.cfcs.foo)</td>
  <td><input type="Text" name="objectName" value=""></td>
 </tr>


 <tr>
  <td>Max Number of Test Cases per File (same function)</td>
  <td><input type="Text" name="maxTestsPerFile" value="1000"> (multiple files will be generated if more than this number)</td>
 </tr> 
 <tr>
  <td>Max Number of Test Cases for a Function</td>
  <td><input type="Text" name="maxTests" value="5000"></td>
 </tr>  
 <tr>
  <td></td>
  <td><input type="submit" name="submit" value="submit"></td>
 </tr> 
 </table>
 </form>



</body>
</html>





Cheers,
B.

Tuesday, July 17, 2012

CF: ColdFusion 10 on Tomcat and the CGI mistery

One of the things I applaud Adobe for in the new release of ColdFusion is to have the stomach to change the  underlying Application container. Moving from JRUN to Tomcat is the right step. However, as part of the move, there still seems to be some mystery surrounding what this means.

As pointed out previously by myself, Rupesh Kumar, has published a good primer on the differences, but also introduced some questions which I did not find answers for. So nothing like the present to go and dig.

More specifically he points out that Adobe has made changes (enhancements) to Tomcat that allow it to work hand in hand with their connectors to get more data to the servlet engine; this, according to him, would not be available if you ran a deployment of CF10 on standard Tomcat.

Of course, I wondered, what in particular would not be available and ran some tests. I used the following scenarios (all on Windows 2008 R2):

a) Standard Install of CF 10 with Adobe ISAPI connector and IIS.
b) Standard Install of CF 10 with BonCode connector and IIS.
c) Tomcat WAR deployment of CF with BonCode connector and IIS.
d) Tomcat WAR Deployment using Tomcat web server (Coyote)

I configured the BonCode connector for best alignment with Adobe as outlined in my previous blog post.
I admit that I did not try to use the Apache Tomcat  regular ISAPI redirector, since it would have not worked for scenario a) and b) and I assume that there are differences there. I also did not try Apache HTTPD webserver scenarios.


I arrived at my conclusions by using a sophisticated setup ;o)
I simply dumped the CGI scope and the count of CGI variables, then compare the dump files. The dump files in the download  zip document reflect the output for the scenarios above.


However, overall I was not able to discover large differences in the CGI scope among the different approaches for running ColdFusion 10. The results for scenarios a) - c) were identical, and while scenario d) showed differences, they were minimal. I attribute this to Tomcat's native web-server's interpretation of HTTP protocol. It chooses to interpret traffic and headers slightly differently.

The differences in scenario d) relate to the following  CGI variables:
Content_Length
Context_Path
Gateway_Interface
HTTPS


Thus, if you wish to keep your coding the same, select options a)-c) while exercising some care in the use of Apache Coyote as webserver for ColdFusion 10.

Cheers,
B.




Thursday, June 28, 2012

CF: ColdFusion 10 experimenting with alternate connector with IIS

A more up-to-date article about this can be found here:
http://www.boncode.net/boncode-connector/using-boncode-with-adobe-coldfusion


If you know me, you know I really don't like the ISAPI connector Apache Tomcat has been using for ages to allow for connectivity between Apache Tomcat and IIS. This annoyed me sooo much I went to create an alternative with the BonCode connector.

When Adobe released ColdFusion 10, however, they built their connection mechanism on top of the original ISAPI connector for IIS. In addition, they made modifications to both the Tomcat server code and the ISAPI connector code to accommodate their particular needs.

The upside of this is things continue to mostly work like they have in the previous iterations, with maybe the only exception being, that you can no longer use CFFLUSH with the out of the box setup. You actually have to disable connector buffering (this has other side effects that you maybe OK with maybe not). To do so go to {CF-Home}/config/wscoonfig/{connector-no}/isapi_redirect.properties. Change iis_buffer_enable to false and restart the IIS.

By now you also know that Adobe does not recommend you use the default Tomcat instance to host anything else besides ColdFusion. A good blog post: "What’s the deal with Tomcat in ColdFusion 10?"  by Rupesh Kumar explains this in more detail.

On the other side, if you want to easily use one IIS to front-end multiple tomcat instances or applications, you really have to do something about the non-standard connector that ships with CF10. Your IIS gies otherwise completely monkey-crazy if you try to work with the Adobe supplied one.

Thus, time for a good experiment. I built in experimental support for the Adobe specific idiosyncrasies to the AJP protocol into the BonCode connector with version 1.0.2 and was curious whether I could get this to work with the release version of CF10.

The first step is actually to install CF10 as usual. Once the server install of CF10 is complete, however, you will need to remove the existing connector via the webserver config tool like so:


Once the removal of the Adobe connector is done, you can download the BonCode connector from RiaForge connector and start the standard install.

Accept all the defaults but once you get to the Tomcat information page, you will need to change the port to 8012 since this is the port CF10 will accept connections under for AJP, like so:



That's all folks. Now you can configure additional sites, and tomcat instances as outlined in the connector manual and your IIS server will happily serve you.

If you want to take this a little further  I would suggest a few tweaks:
a) If you want CFFLUSH support, check the appropriate option during the install of the connector on the options page.This is truly implemented as HTTP flush detection, rather than disabling of buffers and will not cause extra client or network overhead.
b) If you want the CGI.PATH_INFO variable to be populated exactly like CF10, you will need to change the connector setting file slightly. First, find out where the setting file is located by calling this URL on the server:
 http://localhost/a.cfm?BonCodeConnectorVersion=true
Then, add the PathInfoHeader directive like so to the setting file:
<PathInfoHeader>path-info</PathInfoHeader>

c) If you want to use the same packet size that Adobe defaults to, you can also change the packet size using the PacketSize directive in the setting file like so:

<PacketSize>65531</PacketSize>

This reduces the number of packets exchanged between IIS and CF Tomcat but uses a larger buffer block.

d) If you want to explore the full functionality of CF10 I would also recommend that you add the wildcard mappings to the appropriate sub-folders in your  CF10 site. This is used for some of its flex and background features:


/CFFormGateway
/flex2gateway
/CFFileServlet
/cfform-internal
/flashservices/gateway
/flex-internal
/rest


The directions on how to add wilcard mappings are also in the manual that is included in the download package for the BonCode connector.

Happy experimenting.

B.

UPDATE ! UPDATE ! UPDATE ! UPDATE! -- 8/1/2012

I added a setting specifically to address the behavior of the connector for Adobe backends with version 1.0.8 of the connector. This allows the connector to be changed with only one switch and everything should fall into place:


<EnableAdobeMode>True</EnableAdobeMode>


UPDATES: -- 5/30/2013

Adobe made changes that are only reflected in version 1.0.15 of Boncode. If you are using any earlier version you will need to change the server.xml file normally located here:

cf_root/runtime/conf/server.xml

Find line similar to:
<Connector port="8012" protocol="AJP/1.3" redirectPort="8443 packetSize="65531"/>

And add the packetSize directive. This is not needed if you use version 1.0.15 or higher of connector.

UPDATES -- 01/10/2014

More time spent going through the grunge work of analysis when people reported issues more changes discovered.  For example, the Adobe connector hard-codes but hides the packetSize attribute in tomcat. Adobe is actually using 65531 bytes as packet size now. Why? No one knows.
Version 1.0.18 (to be released shortly) of the connector makes this adjustment automatically when you turn on Adobe mode so you don't get odd pages with bytes missing. This is pretty mean. I made changes accordingly to above examples.
If you use standard Tomcat, but switch to Adobe mode, you will need to put this value into the Tomcat server.xml configuration file from now on.

UPDATES -- 02/17/2014

Some hard core testing has gone into release 1.0.18g of the connector and as a result of feedback from the community I have removed the "experimental" moniker for CF10. Will support you all the way now ;o)


UPDATES -- 10/18/2014

Please note that there are some changes to the default port used for Adobe ColdFusion 11. ColdFusion 11 uses port 8014 for AJP traffic.



As usual, feedback is appreciated. Please post on the Riaforge.org site.

Tuesday, June 26, 2012

CF: Installing ColdFusion 10 with IIS

I thought I write a quick note on the installation of CF10 on Windows, especially if you want to use IIS7.
Let's say you installed IIS with the most basic setup and the tried to install CF10.

During the setup of CF10 you will be prompted at the web server selection and configuration step to install more Role Services for IIS like this:



You would correctly interpret this to mean:
Install: ASP.NET, ISAPI Extensions and ISAPI Filter like so:



However, this is not quite accurate. Since you still will not be able to proceed.You will also need to add the CGI Role Service.

Best,
B.

Saturday, June 23, 2012

CF: breaking queries into pages of data

Another day another problem to solve. With many more Ajax type screens, managing the data flow to the front-end needs to be baked into the core system. Otherwise, it seems, the users really do not have a care in the world about how much data they consume....
In my specific case, I am receiving a query (structured data in records), any type of query from any type of DB. I do not know the number of columns, indexes, pks or anything. The job is to be able to return a page of records from this query without going back to the database (would be hard since we don't know the database either). This could even be a QofQ, i.e. code generated, set of records.
I scratched my head several times over this, but still do not think I have an elegant solution. Nonetheless, I have a working one.
I wanted to avoid looping over the query itself and creating a new one record by record. I decided to use a Query of Query approach though only when needed. To do that I dynamically attach another column to the original query that I am using as primary key. Then, I am calculating the start and end records for the page and using a QoQ to just return the ones requested for the Page.


<cffunction name="getQueryPage" access="public" returntype="query" hint="return a query with subset of records based on page argument.">

 <cfargument name="selRecords" required="Yes" type="query" hint="the base query with records">

 <cfargument name="Page" required="no" type="numeric" default="1" hint="which page (chunk) to return">

 <cfargument name="PageSize" type="numeric" default="50" hint="size of record chunk. this is also the max number of records to be returned.">



 <cfscript>

  //init stuff

  var dataQuery = arguments.selRecords; //realias

  var selReturn = dataQuery;

  var iPageCount = ceiling(dataQuery.recordCount/arguments.PageSize);

  var lstCols = dataQuery.ColumnList; // capture col list before change 

  var blnDoSubQuery = false;

  var intStartRec = 0;

  var intEndRec = arguments.PageSize;

  var zzArray = ArrayNew(1); 

   

  

  arguments.Page = int(Abs(arguments.Page)); //ensure that we have no negatives

  //if we request a page that is too big return last page

  if( arguments.Page GT iPageCount) arguments.Page = iPageCount;

  

  //attach control column to base data if calculations require it and or it is not present. 

  if (ListFindNoCase(lstCols,"zzzPageControlCol") IS 0 AND arguments.Page GT 0) {

   intStartRec = 1 + ((arguments.Page -1) * arguments.PageSize);

   intEndRec = intStartRec + (arguments.PageSize -1);   

   //only if the overall query has more records than requested do we need to do anything.

   if ( NOT (intStartRec IS 1 AND intEndRec GTE dataQuery.recordCount)) {

    blnDoSubQuery = true;

    //attach control col

    zzArray = ArrayNew(1);   

    //if we do not think that the passed in query is cached, we can probably use a scheme

    //were we only mark the records that we need to return.

    //however, marking all records one time is faster, if the base query is cached and repeatedly pages

    //need to be returned

    for (i=1; i LTE dataQuery.Recordcount; i++) {

     zzArray[i] = i;

    } 

    QueryAddColumn(dataQuery,"zzzPageControlCol","Integer",zzArray);      

   }; 

  }

 </cfscript>

 <!--- only do subquery if needed, ideally the base query is cached somehow but that is beyond this scope (bsoylu) --->

 <cfif blnDoSubQuery>

  <cfquery name="selReturn" dbtype="query" >

   SELECT #lstCols#

   FROM dataQuery

   WHERE zzzPageControlCol >= #intStartRec# AND zzzPageControlCol <= #intEndRec#

  </cfquery>

 </cfif>

 

 <cfreturn selReturn>

</cffunction>

Cheers,
B.

Friday, May 4, 2012

Sencha: Presentation Slides from SourceDevCon 2012: Combining Sencha Touch 2 and ExtJS4 into one project

They said it could not be done. But for all who attended my talk at Source Dev Con 2012 about Combining your Sencha library projects into one, now know, that we can fight this fight! We even have a reasonable chance to win battles.
First, thanks to all who have taken time to listen and were polite enough not to yawn in front off me (though I did not check when I turned my back).
Second, if things were unclear (sometimes after listening to many people talk ceaselessly ideas get swished around the head and start chasing each other down into a land of fog and confusion ;o) please contact me and I will try my best to respond more clearly.
Here is the sample code.
Here are the presentation slides for your continued entertainment.

Cheers,
B.


Sunday, April 15, 2012

Sencha: Parsing Touch 2

I started an experiment  to see whether the whole JS script code of the Sencha Touch 2 library could be parsed into a JSON representation for further analysis.
After going at it a few different ways I was finally able to do it using the Esprima JS library.

Here is the JSON representation of the code if you are interested and want to take a look yourself.

I used this as part of analysis to compare ExtJS4 and Sencha Touch 2 class libraries. Interesting to find out what code has potential to run unmodified in both of them vs code that probably will not.
Here is the link to resulting analysis spreadsheet (17KB).

Overall we are looking at a good chance that things will not work; 77% of the classes in ExtJS4 do not have a representation in Sencha Touch 2.



Cheers,
B.

Test


Friday, April 13, 2012

Sencha: Charlotte User Group Meeting

Yesterday we had our first Sencha Charlotte Usergroup Meeting. It turned out to be productive discussions around what, when, and how we want to meet.
We also were able to put more meat on the bone for future presentation. Thus, this is shaping up to be a good group and I am looking forward to learn new and interesting things.
Currently we have meetings scheduled until September. Here are the topics that we intend to cover:


  • 5/31 - Expand Designer 2 Demo - Bilal
  • 6/28 - PhoneGap - Jojo
  • 7/26 - MVC in JS (Backbone, Sencha) - Joe 
  • 8/30 - Unit Testing w. Jasmin/ QUnit   -  Pramod
  • 9/27 - Sencha GWT   -  Brad


More detail for each of these meetings is available on our group meetup site.

Cheers,
B.

Friday, January 13, 2012

CF: When does OnRequestEnd get executed

This is one is from my main man Kip (@kipthegreat) he ran through this exercise using Adoce CF 9.
Might be helpful for others to know.


Situation
Does OnRequestEnd() get executed?
Page calls <cfabort> tag
Yes
Page calls to redirect user
Yes
Page calls with abort="true"
Yes
Request exceeds timeout
NO
Uncaught exception
NO


The lesson to pay attention to is to catch and handle your exceptions ;o)

 Cheers,
-B


Sunday, January 8, 2012

JavaScript: Check whether a sub object is defined in a multi-object chain

When you are in JavaScript mode it is common for you to use
typeof operator to see whether a given variable, object, function etc. is known to JavaScript.

However, many times I find myself writing more complex code because it is a child of a child object that I need to check for a value. This makes for ugly code.
Being rather simple minded I chose to Google for an obvious solution. Unfortunately, nothing was immediately available (or I might be simple missing it). Most people seem to only need to be dealing with the top level object deceleration and, thus, no need for anything else.

So, to make a long story short, I created a simple helper function that does most of the work for me and cleans up the repetitive code. Feel free to use it at your leisure:

/**

 * Take string input in varName and determine whether it is defined object in Javascript

 * @param {String} varName

 * @return {boolean}

 * @author Bilal Soylu

 */

function isDefined(varName) {

 var retStatus = false;

 

 if (typeof varName == "string") {

  try {

   var arrCheck = varName.split(".");

   var strCheckName = "";

   for (var i=0; i < arrCheck.length; i++){

    strCheckName = strCheckName + arrCheck[i];

    //check wether this exist

    if (typeof eval(strCheckName) == "undefined") {

     //stop processing

     retStatus = false;

     break;     

    } else {

     //continue checking next node

     retStatus = true;

     strCheckName = strCheckName + ".";

    }

   }

  } catch (e) {

   //any error means this var is not defined

   retStatus = false;

  }

  

 } else {   

  throw "the varName input must be a string like myVar.someNode.anotherNode[]";

 }

 

 return retStatus;

} 

Cheers,
-B.

Monday, January 2, 2012

Java: Implementing PGP Single Pass Sign and Encrypt using League of Bouncy Castle library

The League of Bouncy Castle Cryptography library is chock-full of goodies but it is hard to convert what is in there to more practical examples.
The example files are a solid basis but I seam to need to fiddle quite a bit until it something is usable for me. The PGP Single Pass Sign and Encrypt process is one of these things that took me for a long time to figure out. I owe much of the actual solution impementation to John Opincar who solved this puzzle for C#.
Here is my implementation for Java:




/**
 * 
 */
package net.boncode.crypto;

//bouncy castle imports
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;

//java imports
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Date;
import java.util.Iterator;

/**
 * @author Bilal Soylu
 * 
 */

public class OnePassSignatureProcessor {

 /**
  * This is the primary function that will create encrypt a file and sign it
  * with a one pass signature. This leans on an C# example by John Opincar
  * @author Bilal Soylu
  * @param targetFileName
  *            -- file name on drive systems that will contain encrypted content
  * @param embeddedFileName
  *            -- the original file name before encryption
  * @param secretKeyRingInputStream
  *            -- Private Key Ring File
  * @param targetFileStream
  *            -- The stream for the encrypted target file
  * @param secretKeyPassphrase
  *            -- The private key password for the key retrieved from
  *            collection used for signing
  * @param signPublicKeyInputStream
  *            -- the public key of the target recipient to be used to
  *            encrypt the file
  * @throws Exception
  */
 public void fEncryptOnePassSignatureLocal(String targetFileName,
   String embeddedFileName, InputStream secretKeyRingInputStream,
    OutputStream targetFileStream, String secretKeyPassphrase,   
   InputStream signPublicKeyInputStream, InputStream contentStream) throws Exception {
  // ** INIT
  // read public Key from stream (file, if keyring we use the first working key)
  PGPPublicKey encKey = readPublicKey(signPublicKeyInputStream);
  // need to convert the password to a character array
  char[] password = secretKeyPassphrase.toCharArray();
  int BUFFER_SIZE = 1 << 16; // should always be power of 2(one shifted bitwise 16 places)
  //for now we will always do integrity checks and armor file
  boolean armor = true;
  boolean withIntegretyCheck = true;
  //set default provider, we will pass this along
  BouncyCastleProvider bcProvider = new BouncyCastleProvider();

  // armor stream if set
  if (armor)
   targetFileStream = new ArmoredOutputStream(targetFileStream);

  // Init encrypted data generator
  PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(
    SymmetricKeyAlgorithmTags.CAST5, withIntegretyCheck,
    new SecureRandom(), bcProvider);
  encryptedDataGenerator.addMethod(encKey);
  OutputStream encryptedOut = encryptedDataGenerator.open(targetFileStream,new byte[BUFFER_SIZE]);

  // start compression
  PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(
    CompressionAlgorithmTags.ZIP);
  OutputStream compressedOut = compressedDataGenerator.open(encryptedOut);

  //start signature
  //PGPSecretKeyRingCollection pgpSecBundle = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(secretKeyRingInputStream));
  //PGPSecretKey pgpSecKey = pgpSecBundle.getSecretKey(keyId);
  PGPSecretKey pgpSecKey = readSecretKey(secretKeyRingInputStream);
  if (pgpSecKey == null)
   throw new Exception("No secret key could be found in specified key ring collection.");
  PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(password,bcProvider);

  PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
    pgpSecKey.getPublicKey().getAlgorithm(),
    HashAlgorithmTags.SHA1, bcProvider);
  
  signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
  // iterate to find first signature to use
  for (@SuppressWarnings("rawtypes")
  Iterator i = pgpSecKey.getPublicKey().getUserIDs(); i.hasNext();) {
   String userId = (String) i.next();
   PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
   spGen.setSignerUserID(false, userId);
   signatureGenerator.setHashedSubpackets(spGen.generate());
   // Just the first one!
   break;
  }
  signatureGenerator.generateOnePassVersion(false).encode(compressedOut);

  // Create the Literal Data generator output stream
  PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
  // get file handle
  File actualFile = new File(targetFileName);
  // create output stream
  OutputStream literalOut = literalDataGenerator.open(compressedOut,
    PGPLiteralData.BINARY, embeddedFileName,
    new Date(actualFile.lastModified()), new byte[BUFFER_SIZE]);
  
  
  // read input file and write to target file using a buffer
  byte[] buf = new byte[BUFFER_SIZE];
  int len;
  while ((len = contentStream.read(buf, 0, buf.length)) > 0) {
   literalOut.write(buf, 0, len);
   signatureGenerator.update(buf, 0, len);
  }
  // close everything down we are done
  literalOut.close();
  literalDataGenerator.close();
  signatureGenerator.generate().encode(compressedOut);
  compressedOut.close();
  compressedDataGenerator.close();
  encryptedOut.close();
  encryptedDataGenerator.close();
  

  if (armor) targetFileStream.close();

 }
 /**
  * Try to find a public key in the Key File or Key Ring File
  * We will use the first one for now.
  * @author Bilal Soylu
  * @param in -- File Stream to KeyRing or Key
  * @return first public key
  * @throws IOException
  * @throws PGPException
  */
 private static PGPPublicKey readPublicKey(InputStream in)
   throws IOException, PGPException {
  in = PGPUtil.getDecoderStream(in);

  PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in);

  //
  // we are only looking for the first key that matches
  //

  //
  // iterate through the key rings.
  //
  Iterator rIt = pgpPub.getKeyRings();

  while (rIt.hasNext()) {
   PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();
   Iterator kIt = kRing.getPublicKeys();

   while (kIt.hasNext()) {
    PGPPublicKey k = (PGPPublicKey) kIt.next();

    if (k.isEncryptionKey()) {
     return k;
    }
   }
  }

  throw new IllegalArgumentException(
    "Can't find encryption key in key ring.");
 }

 
 
 /**
  * Find first secret key in key ring or key file. 
  * A secret key contains a private key that can be accessed with a password.
  * @author Bilal Soylu
  * @param in -- input Key file or key ring file
  * @param passwd -- password for key
  * @return matching private key
  * @throws IOException
  * @throws PGPException
  * @throws NoSuchProviderException
  */
 private static PGPSecretKey readSecretKey(InputStream in)
   throws IOException, PGPException, NoSuchProviderException {
  
  PGPSecretKey               sKey = null;
  try {
   in = PGPUtil.getDecoderStream(in);
   PGPSecretKeyRingCollection pgpPriv = new PGPSecretKeyRingCollection(in);
 
   // we just loop through the collection till we find a key suitable for
   // decrypt
   Iterator  it = pgpPriv.getKeyRings();       
   PGPSecretKeyRing   pbr = null;
 
         while (sKey == null && it.hasNext())
         {
          Object readData = it.next();
          if (readData instanceof PGPSecretKeyRing) {           
           pbr = (PGPSecretKeyRing)readData;             
              sKey =  pbr.getSecretKey();
             }
         }
         
         if (sKey == null)
         {
             throw new IllegalArgumentException("secret key for message not found.");
         }
  }
        catch (PGPException e)
        {
            System.err.println(e);
            if (e.getUnderlyingException() != null)
            {
                e.getUnderlyingException().printStackTrace();
            }
        }
        return sKey; 
 } 
 
 

 /**
  * fDecryptOnePassSignature will decrypt a file that was encrypted using
  * public key, then signed with a private key as one pass signature based on
  * example of verifyAndDecrypt() by Raul
  * 
  * @param encryptedInputStream
  * @param signPublicKeyInputStream
  * @param secretKeyInputStream
  * @param secretKeyPassphrase
  * @return
  * @throws Exception
  */
 public void fDecryptOnePassSignatureLocal(InputStream encryptedInputStream,
   InputStream signPublicKeyInputStream,
   InputStream secretKeyInputStream, String secretKeyPassphrase,
   OutputStream targetStream) throws Exception {

  Security.addProvider(new BouncyCastleProvider());

  // The decrypted results.
  // StringBuffer result = new StringBuffer();
  // The private key we use to decrypt contents.
  PGPPrivateKey privateKey = null;
  // The PGP encrypted object representing the data to decrypt.
  PGPPublicKeyEncryptedData encryptedData = null;

  // Get the list of encrypted objects in the message. The first object in
  // the
  // message might be a PGP marker, however, so we skip it if necessary.
  PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(encryptedInputStream));
  Object firstObject = objectFactory.nextObject();
  System.out.println("firstObject is " + firstObject);
  PGPEncryptedDataList dataList = (PGPEncryptedDataList) (firstObject instanceof PGPEncryptedDataList ? firstObject
    : objectFactory.nextObject());

  // Find the encrypted object associated with a private key in our key
  // ring.
  @SuppressWarnings("rawtypes")
  Iterator dataObjectsIterator = dataList.getEncryptedDataObjects();
  PGPSecretKeyRingCollection secretKeyCollection = new PGPSecretKeyRingCollection(
    PGPUtil.getDecoderStream(secretKeyInputStream));
  while (dataObjectsIterator.hasNext()) {
   encryptedData = (PGPPublicKeyEncryptedData) dataObjectsIterator.next();
   System.out.println("next data object is " + encryptedData);
   PGPSecretKey secretKey = secretKeyCollection.getSecretKey(encryptedData.getKeyID());
   
   if (secretKey != null) {
    // This object was encrypted for this key. If the passphrase is
    // incorrect, this will generate an error.
    privateKey = secretKey.extractPrivateKey(secretKeyPassphrase.toCharArray(), "BC");
    break;
   }
  }

  if (privateKey == null) {
   System.out.println();
   throw new RuntimeException("secret key for message not found");
  }

  // Get a handle to the decrypted data as an input stream
  InputStream clearDataInputStream = encryptedData.getDataStream( privateKey, "BC");
  PGPObjectFactory clearObjectFactory = new PGPObjectFactory( clearDataInputStream);
  Object message = clearObjectFactory.nextObject();

  System.out.println("message for PGPCompressedData check is " + message);

  // Handle case where the data is compressed
  if (message instanceof PGPCompressedData) {
   PGPCompressedData compressedData = (PGPCompressedData) message;
   objectFactory = new PGPObjectFactory(compressedData.getDataStream());
   message = objectFactory.nextObject();
  }

  System.out.println("message for PGPOnePassSignature check is " + message);

  PGPOnePassSignature calculatedSignature = null;
  if (message instanceof PGPOnePassSignatureList) {
   calculatedSignature = ((PGPOnePassSignatureList) message).get(0);
   PGPPublicKeyRingCollection publicKeyRingCollection = new PGPPublicKeyRingCollection(
     PGPUtil.getDecoderStream(signPublicKeyInputStream));
   PGPPublicKey signPublicKey = publicKeyRingCollection
     .getPublicKey(calculatedSignature.getKeyID());
   calculatedSignature.initVerify(signPublicKey, "BC");
   message = objectFactory.nextObject();
  }

  System.out.println("message for PGPLiteralData check is " + message);

  // We should only have literal data, from which we can finally read the
  // decrypted message.
  if (message instanceof PGPLiteralData) {
   InputStream literalDataInputStream = ((PGPLiteralData) message).getInputStream();
   int nextByte;

   while ((nextByte = literalDataInputStream.read()) >= 0) {
    // InputStream.read guarantees to return a byte (range 0-255),
    // so we
    // can safely cast to char.
    calculatedSignature.update((byte) nextByte); // also update
                // calculated
                // one pass
                // signature
    // result.append((char) nextByte);
    // add to file instead of StringBuffer
    targetStream.write((char) nextByte);
   }
   targetStream.close();
  } else {
   throw new RuntimeException("unexpected message type " + message.getClass().getName());
  }

  if (calculatedSignature != null) {
   PGPSignatureList signatureList = (PGPSignatureList) objectFactory.nextObject();
   System.out.println("signature list (" + signatureList.size() + " sigs) is " + signatureList);
   PGPSignature messageSignature = (PGPSignature) signatureList.get(0);
   System.out.println("verification signature is " + messageSignature);
   if (!calculatedSignature.verify(messageSignature)) {
    throw new RuntimeException("signature verification failed");
   }
  }

  if (encryptedData.isIntegrityProtected()) {
   if (encryptedData.verify()) {
    System.out.println("message integrity protection verification succeeded");
   } else {
    throw new RuntimeException("message failed integrity check");
   }
  } else {
   System.out.println("message not integrity protected");
  }

  //close streams
  clearDataInputStream.close();
  
  
 }

}




Cheers,
B.