tag:blogger.com,1999:blog-86885786826388537632024-03-05T15:56:54.783-05:00BonCodeBilal's view on tech and code (Java, C#, vb, cfml, JavaScript, NodeJS, etc, etc.) and some random other musings.Unknownnoreply@blogger.comBlogger93125tag:blogger.com,1999:blog-8688578682638853763.post-55045466966253763772020-10-16T11:11:00.001-04:002020-10-16T11:11:17.493-04:00LG V35 Home Screen Crashing with "Home is not responding." message<p>I like my LG V 35 Android phone. It is fast, has a kick-ass sound chip and worked perfectly for the </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZlEADZy6J59QULwJCZcob4kZ9jIersOACUgRkaGz8EEUrbkFhdNRwI8m_u9BRIXr3eP5EvotOvYRaeeuQ2CwbIB0h2HQeZYGIDMWWIkw6xLZ0co3JBDjrHAFYpENQhE7E6suWtwkjmwM/s558/LGV35.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="558" data-original-width="558" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZlEADZy6J59QULwJCZcob4kZ9jIersOACUgRkaGz8EEUrbkFhdNRwI8m_u9BRIXr3eP5EvotOvYRaeeuQ2CwbIB0h2HQeZYGIDMWWIkw6xLZ0co3JBDjrHAFYpENQhE7E6suWtwkjmwM/w200-h200/LGV35.jpg" width="200" /></a></div><br />longest time. But, all of a sudden it would not open the Home screen (pressing on circle from bottom phone menu) anymore.<p></p><p>This should take half a second at most, it started taking 30s plus, with screen turning black. The Home screen would, then, completely reload.</p><p>The message "Home screen is not responding. Do you want to continue to wait or quit?" would sometimes appear.</p><p>I rebooted and it would work for a bit. Started to delete downloaded apps. Looking at Internet boards to see if this problem has been encountered by others. It did not seem like others were seeing this.</p><p>I was getting desperate since the only other solution suggested was to reset phone back to factory settings.</p><p>Last try using safe-mode to double check things. Unfortunately, the behavior did not go away in safe-mode. This made me look into the core applications that are shipped with the phone, since in safe-mode, nothing else is running.</p><p>More twiddling and experimenting followed.</p><p>The solutions was simple but indicated a bug in the Google Mail application when working with Office 365 email accounts. I removed a Microsoft Office 365 account I had recently added. When Google Mail was connected to this account it would hang up the phone's home screen. Making the phone nearly unusable since you cannot effective navigate between apps.</p><p>Odd.</p><p>I downloaded the Office Outlook for Mobile app for my office 365 account and my phone works again. I can check the email without hanging up my phone.</p><p>If you are finding yourself in a similar situation I hope this helps.</p><p>Cheers,</p><p>Bilal</p><p><br /></p><div><br /></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-11864346931055453612020-06-16T14:28:00.003-04:002020-06-16T14:28:42.893-04:00ReactNative: Quickly integrate contactless payments into your react-native projects<h1 id="quickly-integrate-contactless-payments-into-your-react-native-projects">
Quickly integrate contactless payments into your react-native projects</h1>
Trying to go out an build a contactless payment solution from scratch is not something that everyone has the time and gumption to do.
As far as approach there are principally two successful approaches to contactless one can follow. The RFC and the QR methodology.<br />
<strong>The RFC Methodology</strong><br />
This ist the incumbent process in most of Europe and North America. Buyers use an RFC chip based Credit or Debit card or mobile device. Buyers swipe the device over a specialized card reader and seller, then, connects to card or payment processor.<br />
Popular implementations of this in the US for mobile devices are represented by Apple Pay and Google Pay. The issue for us what that this method had a few drawbacks:<br />
<ul>
<li>
We needed to have special hardware. Some of which has be rented on a monthly basis by the seller (merchant).<br />
</li>
<li>
It really wasn't completely touch free. Debit cards still required touching the seller terminals. Other terminals required confirmations on occasion.<br />
</li>
<li>
Getting up and running in a real world store was not a matter of hours, but weeks and months.<br />
</li>
</ul>
The advantages were that users mostly are familiar with the payment processes in the US.<br />
<strong>The QR Methodology</strong><br />
This is the Asian model and very popular in China. Both AliPay and WeChat pay and some others use this as their standard of starting the payment processing cycle.<br />
The drawbacks here are users in US and Europe are less familiar with this methodology then they are with others though this is quickly changing. We see more users becoming familiar with QRs for other purposes besides advertising. Users now see them in restaurants for menu lookup and on rentals when renting scooters or bikes.<br />
Thus the drawbacks:<br />
<em>a)</em> User familiarity with process<br />
<em>b)</em> Since processing occurs on different device than payment initiation closing the payment loop requires effort on programmers.<br />
The advantages are that this, when done right, is:<br />
<em>a)</em> Very quick to implement since no other hardware is needed<br />
<em>b)</em> Very inexpensive for seller (merchants)<br />
<em>c)</em> Can be truly contactless even with debit cards<br />
<em>d)</em> Can close the payment loop even when process is distributed across devices.<br />
Thus, in this tutorial we will focus on QR based contactless payments and show you how to <strong>quickly</strong> get started using them in your react-native based mobile applications.<br />
We use the XcooBee contactless payments since it needs very little effort while providing many options as well as the ability to close the payment notification loop. capabilities and programming in the app.<br />
<h3 id="getting-started-from-scratch">
Getting Started from Scratch</h3>
<h4 id="prerequisites">
Prerequisites:</h4>
<ul>
<li><a href="https://nodejs.org/">Node 12</a></li>
<li>Free <a href="https://www.stripe.com/">Stripe</a> account</li>
<li>Free <a href="https://app.xcoobee.net/auth/signup">XcooBee Professional</a> account</li>
</ul>
<h4 id="setting-up-payment-processing-backend">
Setting up Payment Processing Backend</h4>
In our example, you will need a <a href="https://app.xcoobee.net/auth/signup">XcooBee Professional</a> account and a <a href="https://www.stripe.com/">Stripe</a> account. Both of these are free to setup. Use the <a href="https://www.youtube.com/watch?v=oRyl3qlp7bQ">video tutorials</a> or <a href="https://www.xcoobee.com/docs/how-to/creating-your-first-xcoobee-project/">web tutorial</a> to configure your first XcooBee Payment Project. This sets up you base infrastructure to process payments for your app. This is what you will need to do all the back-end processing.<br />
<h3 id="the-app">
The App</h3>
You can use create react native (CRA) or Expo project as baseline for our example. We will use Expo based version.<br />
If you have never installed expo first install the command line tools:<br />
<pre class="hljs"><code><div>
npm install expo-cli --global
</div>
</code></pre>
<h4 id="create-your-expo-project">
Create Your Expo Project</h4>
Creating your expo project is also straight forward.<br />
<code>expo init myPayProject</code> => select blank (with Typescript)<br />
<pre class="hljs"><code><div>
cd myPayProject
expo start
</div>
</code></pre>
The last command should start a expo session with your expo app.<br />
<h4 id="install-libraries">
Install libraries</h4>
Now that we have a base app lets add the needed libraries<br />
<code>yarn add react-native-svg</code> => adds needed SVG support for vector graphics<br />
<code>yarn add @xcoobee/react-native-xcoobee-payment-sdk</code> => adds the XcooBee libraries<br />
<h4 id="apptsx">
App.tsx</h4>
There is only one file that we need to change. That is <code>App.tsx</code>.<br />
Add import statement to your import section.
<code>import XcooBeePaySDK from '@xcoobee/react-native-xcoobee-payment-sdk';</code><br />
Follow this with initial configuration of the payment SDK. Here is where you could set Expo Install Id etc. if you need to communicate back to your device about success and failure. For our purposes the only needed config items are <code>campaignId</code> and <code>formId</code>. You can find these values in your XcooBee Payment Project summary page. Replace your values instead of using the example values.<br />
<pre class="hljs"><code><div>
XcooBeePaySDK.setSystemConfig({
<span class="hljs-attr">campaignId</span>: <span class="hljs-string">'e98.eg0000000'</span>,
<span class="hljs-attr">formId</span>: <span class="hljs-string">'t000'</span>
});
</div>
</code></pre>
The remainder of the code is only a few <code><Text></code> to display labels, one <code><TextInput></code> to have the user enter the amount they wish to charge. The default is $1.<br />
Your <code>App.tsx</code> should look like this:<br />
<pre class="hljs"><code><div>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { StyleSheet, Text, TextInput, View } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-native'</span>;
<span class="hljs-keyword">import</span> XcooBeePaySDK <span class="hljs-keyword">from</span> <span class="hljs-string">'@xcoobee/react-native-xcoobee-payment-sdk'</span>;
<span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> replace with actual values from XcooBee Payment Project</span>
<span class="hljs-comment">// Open your payment project in edit mode and review the summary screen</span>
XcooBeePaySDK.setSystemConfig({
<span class="hljs-attr">campaignId</span>: <span class="hljs-string">'e98.eg0000000'</span>,
<span class="hljs-attr">formId</span>: <span class="hljs-string">'t000'</span>
});
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">const</span> [chargeAmount, setText] = React.useState(<span class="hljs-string">'1.00'</span>);
<span class="hljs-keyword">const</span> XcooBeePayQR = XcooBeePaySDK.createPayQR(<span class="hljs-built_in">parseFloat</span>(chargeAmount));
<span class="hljs-keyword">return</span> (
<span class="xml"><span class="hljs-tag"><<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.container}</span>></span>
<span class="hljs-tag"><<span class="hljs-name">Text</span>></span>
How much do you want to charge:
<span class="hljs-tag"></<span class="hljs-name">Text</span>></span>
<span class="hljs-tag"><<span class="hljs-name">TextInput</span>
<span class="hljs-attr">defaultValue</span>=<span class="hljs-string">'1.00'</span>
<span class="hljs-attr">style</span>=<span class="hljs-string">{{textAlign:</span>'<span class="hljs-attr">right</span>' }}
<span class="hljs-attr">onChangeText</span>=<span class="hljs-string">{text</span> =></span> setText(text)}
value={chargeAmount}
/>
<span class="hljs-tag"><<span class="hljs-name">Text</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
<span class="hljs-attr">marginBottom:</span> <span class="hljs-attr">20</span>, <span class="hljs-attr">marginTop:</span> <span class="hljs-attr">20</span>
}}></span>Please scan and pay<span class="hljs-tag"></<span class="hljs-name">Text</span>></span>
{XcooBeePayQR}
<span class="hljs-tag"><<span class="hljs-name">Text</span>></span>powered by XcooBee<span class="hljs-tag"></<span class="hljs-name">Text</span>></span>
<span class="hljs-tag"></<span class="hljs-name">View</span>></span></span>
);
}
</div>
</code></pre>
Most of this is boilerplate generated by Expo during project creation.
With this line we define the QR object and amount we want to represent:
<code>const XcooBeePayQR = XcooBeePaySDK.createPayQR(parseFloat(chargeAmount)</code> where the <code>createPayQR</code> is the main function from the Payment SDK and <code>chargeAmount</code> is the amount we wish to charge buyer-user. See the Payment SDK documentation for additional options of types of QRs that can be generated and call options.<br />
When we wish to render we use the QR object like this <code>{XcooBeePayQR}</code>.<br />
This will add the QR in your current render container.<br />
Here is the application when running (this example uses invalid project codes so the payment cycle cannot be started):<br />
<img alt="Example react native app running" src="https://www.xcoobee.com/wp-content/uploads/2020/06/QR-Pay-3-1.png" /><br />
<h3 id="using-our-pre-build-example">
Using our Pre-Build example</h3>
Of course we have already pre-build app you can experiment with if you do not want to go through all the steps. You will still need a XcooBee Payment Project setup completed if you want to process payments (test or live).<br />
Please replace the <code>campaign Id</code> and <code>form id</code> in <code>App.tsx</code><br />
<a href="https://github.com/XcooBee/example-payment-sdk-react-native">Expo Code GitHub Example</a><br />
You can also clone this to your local machine like so:<br />
<code>git clone https://github.com/XcooBee/example-payment-sdk-react-native.git</code><br />
<h2 id="congratulations">
Congratulations</h2>
This is all it takes to have an app that can accept contactless payments.<br />
Of course, there are many more options for QR and direct Payment URL generations than this sample can show. Feel free to experiment with the XcooBee Payment SDK, XcooBee Professional Account, and Stripe or Paypal payment processing.<br />
Pro tip: If you use Stripe test accounts or Paypal Sandbox accounts you will not incur any XcooBee charges either.<br />
You can experiment and refine from here.<br />
<br />
Cheers,<br />
BilalUnknownnoreply@blogger.com2tag:blogger.com,1999:blog-8688578682638853763.post-63520175447888639662017-04-24T20:25:00.001-04:002017-04-24T20:27:18.972-04:00node: creating a debugger setup for visual studio code and mochajs for typescript testsThe title of the post says everything that I wanted to say. I have been looking at how to create a more streamlined environment for my nodejs typescript development.<br />
I was specifically interested in how to start only a specific test with the interactive debugger. I did not want to change the launch config every time I worked on a new test and I also wanted to handle mocha test written in typescript.<br />
<br />
I normally have a tsc process started in separate command window running<span style="font-family: "courier new" , "courier" , monospace;"> tsc -w</span> command.This watches all files and compiles when needed.<br />
<br />
Here are the changes I made to Visual Studio Code and my environment.<br />
In my project I installed:<br />
<br />
<ul>
<li>typescript</li>
<li>mocha</li>
<li>mocha-typescript</li>
</ul>
<br />
<br />
I decided to hide all TypeScript generated files via adding a files.exclude directive to my USER SETTING (access via File:Preferences:Settings), like so:<br />
<br />
<div style="background-color: #1e1e1e; color: #d4d4d4; font-size: 14px; line-height: 19px; white-space: pre;">
<div>
<span style="font-family: "courier new" , "courier" , monospace;">"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/*.js.map": true,
"**/*.js": { "when": "$(basename).ts"}
}</span>
</div>
</div>
<br />
Then came the harder part. Finding a launch.config elements that would work (access via Debug:Open Configurations). I wanted to be able to open the debug pane and open the typescript test file I was working on, set breakpoints and click on "Start Debugging" button (green play button) and have the process kick off correctly.<br />
<br />
This was not as trivial as I initially envisioned and much googling and blog reading ensued.<br />
Here is the launch.config segment that worked for me.<br />
<br />
<br />
<div style="background-color: #1e1e1e; color: #d4d4d4; font-size: 14px; line-height: 19px; white-space: pre;">
<div>
<span style="font-family: "courier new" , "courier" , monospace;">
{
"type": "node",
"request": "launch",
"name": "Debug TS mocha",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"stopOnEntry": false,
"args": ["${fileBasenameNoExtension}.js","--no-timeouts", "--trace-warnings","--colors"],
"cwd": "${fileDirname}",
"sourceMaps": true,
"outFiles": [],
"env": { "NODE_ENV": "testing"}
}
</span>
</div>
</div>
<br />
<br />
This works well with standard mocha js test as well as typescript test files. For typescript you need to either have a watcher going or add a preprocess to the launch.config that transpiles your typescript first.<br />
<br />
Hope this will save someone the head scratching I went though.<br />
<br />
Cheers,<br />
B.<br />
<br />
<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-16538892679092667602017-03-09T16:49:00.000-05:002017-03-09T16:49:03.811-05:00node: A deeper look into npm (Node Package Manager)<div style="animation-delay: 0.1s; animation-duration: 0.1s; animation-iteration-count: 1; animation-name: fontfix; animation-timing-function: linear; background-color: white; color: rgba(0, 0, 0, 0.870588); font-family: "Graphik Meetup", helvetica, arial, sans-serif; font-size: 15px; margin-bottom: 16px; padding: 0px;">
Our topic for our last NodeJs user-group meeting was npm.</div>
<div style="animation-delay: 0.1s; animation-duration: 0.1s; animation-iteration-count: 1; animation-name: fontfix; animation-timing-function: linear; background-color: white; color: rgba(0, 0, 0, 0.870588); font-family: "Graphik Meetup", helvetica, arial, sans-serif; font-size: 15px; margin-bottom: 16px; padding: 0px;">
npm is automatically included when Node.js is installed. npm consists of a command line client and a remote repository. The command line client, of course, interacts with the remote registry. This combination allows users to consume and distribute JavaScript modules that are available in the repo. It was created in response to what its creator said "module packaging done terribly". There are many elements in this toolbox, did you know you can use npm without node??</div>
<div style="animation-delay: 0.1s; animation-duration: 0.1s; animation-iteration-count: 1; animation-name: fontfix; animation-timing-function: linear; background-color: white; color: rgba(0, 0, 0, 0.870588); font-family: "Graphik Meetup", helvetica, arial, sans-serif; font-size: 15px; margin-bottom: 16px; padding: 0px;">
Here are the slides of our deep dive. Hopefully it gave all of you who attended a better understanding a few helpful tips and tricks.</div>
<div style="animation-delay: 0.1s; animation-duration: 0.1s; animation-iteration-count: 1; animation-name: fontfix; animation-timing-function: linear; background-color: white; color: rgba(0, 0, 0, 0.870588); font-family: "Graphik Meetup", helvetica, arial, sans-serif; font-size: 15px; margin-bottom: 16px; padding: 0px;">
<a href="http://boncode.net/downloads/node_package_manager_meetup.pdf" target="_blank"><b>Slides</b></a></div>
<div style="animation-delay: 0.1s; animation-duration: 0.1s; animation-iteration-count: 1; animation-name: fontfix; animation-timing-function: linear; background-color: white; color: rgba(0, 0, 0, 0.870588); font-family: "Graphik Meetup", helvetica, arial, sans-serif; font-size: 15px; margin-bottom: 16px; padding: 0px;">
Best,</div>
<div style="animation-delay: 0.1s; animation-duration: 0.1s; animation-iteration-count: 1; animation-name: fontfix; animation-timing-function: linear; background-color: white; color: rgba(0, 0, 0, 0.870588); font-family: "Graphik Meetup", helvetica, arial, sans-serif; font-size: 15px; margin-bottom: 16px; padding: 0px;">
B.</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-34227452454397770352016-09-15T12:37:00.001-04:002016-09-15T12:37:19.942-04:00nodejs: Continuous Code Deployment using Node.js and AWS StackYesterday we had our monthly Node.js usergroup meetup. I want to thank all for coming and exchanging interesting ideas for us to ponder.<br />
<br />
We looked into how to create a complete deployment cycle using cloud technologies to support our node development.<br />
<br />
We reviewed the complete process (build, test, deploy) and how tools like:<br />
- AWS CloudPipeline<br />
- AWS CodeCommit <br />
- EBS (Elastic Bean Stalk)<br />
<br />
We also used Solano Labs CI build service which is a cloud based alternative to build tool like Jenkins.<br />
<br />
Building a continuous deployment cycle can change the game for even small shops to roll out quality code from development to production in very little time.<br />
<br />
Here are the slides from the presentation, this will probably help those that were taking diligent notes. Most command line text are present on the slides.<br />
<br />
<a href="http://downloads.boncode.net/downloads/CharlotteNodeJSSeptember2016.pdf">http://downloads.boncode.net/downloads/CharlotteNodeJSSeptember2016.pdf</a><br />
<br />
Cheers.<br />
B.<br />
<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-66378765424036544142016-09-10T13:52:00.002-04:002016-09-11T15:57:51.431-04:00serverless: Serverless Framework and ethics vs the shiny object dilemmaFor the last little while I was working on serverless concepts and exploring the <a href="http://serverless.com/" target="_blank">Serverless Framework</a>. It allows me to automate many manual steps but at the same time it also imposes its way of thinking. Thus, it is a highly opinionated framework.<br />
<br />
Though one can be of different opinions some of the good things provided by this framework are the ability to organize your project locally, abstract configuration, and deploy to different clouds from the command line.<br />
<br />
The local development and testing paradigm is still rough and docs and examples are rather high level in many places.<br />
<br />
It is also very much at the beginning stages with version 1.0 just about to be released. As such it has an enthusiastic crew with a lot of passion to push things forward. So, in short it is the new shiny thing that is cool and thus enjoys mindshare and interest.<br />
<br />
In general that is a good thing. In particular, I can already see that there are cracks in the foundation. Two things in particular come to mind:<br />
<br />
<h3>
A) Dogma </h3>
<br />
There is a sense of "we know better what you need" that permeates much of the later work on this framework. Whether it is through heavy configuration driven mechanisms in lieu of any convention elements or the careless breaking of implementation from earlier beta releases. For example, here is just a sprinkling of frameworks that believe in less configuration is better:<br />
<br />
<br />
<ul>
<li>Apache Maven</li>
<li>Appcelerator's Titanium Alloy</li>
<li>ASP.NET MVC</li>
<li>CakePHP</li>
<li>ColdBox Platform</li>
<li>Contao</li>
<li>Crosslight</li>
<li>Durandal (JavaScript SPA Framework)</li>
<li>Ember.js</li>
<li>Enduro.js</li>
<li>Grails</li>
<li>Java Platform, Enterprise Edition</li>
<li>Laravel</li>
<li>Lift (web framework)</li>
<li>Meteor (web framework)</li>
<li>Play Framework</li>
<li>Roxy rest-API</li>
<li>Ruby on Rails</li>
<li>Sails (web framework)</li>
<li>Spring Framework</li>
<li>Symfony</li>
<li>Yii</li>
</ul>
<br />
<br />
This does not appear to be relevant to the current team and my fear is that the shininess will not overcome the effort it takes to maintain this when an alternate framework emerges. Ignorance in this case is not bliss.<br />
<br />
<br />
<h3>
B) Ethics of Tracking</h3>
<br />
The framework uses automatic enabled tracking of usage data. To discover this you will need to dig deep into docs and code. I understand that this is anonymous, however, this is not the way to approach the issue. Take Apple's location tracking for example, it is anonymous as well. How good do you feel about it?<br />
<br />
For me the issue is with the <b>enabled by default attitude.</b> It is the complete undermining of faith of the user community. I believe firmly that any company making the automatic assumption of collecting data about me is living on the edge of ethics. Rather than playing in the muck, I would like to encourage the team to elevate themselves from questionable practices. There is time, and there are better alternatives.<br />
<br />
<br />
Yes, you can disable all this once you find out where in the documentation it is hidden and run the right commands. This is not the point. If you don't ask me assume that you should not do it.<br />
<br />
Here is the disable command if you are looking for it (run against all instances and all machines that you own):<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">$ serverless tracking --disable</span><br />
<br />
<br />
<h3>
Conclusions</h3>
<br />
So in short, this framework has potential but it is sliding into the area of big, obtuse, and rather in-the-way-of-the-task with ethical dangerous undertones before even hitting version one. These are achievements we should not be proud off given the great promise that it has.<br />
<br />
If you have a project that is small or medium sized there are alternatives that work equally well and don't involve you sharing your personal command history. You still get automation and deployment in place with full control. Look at connecting AWS Code Commit to Lambda through Continuous Integration Pipelines via CodePipeline. That is some sweet stuff.<br />
<br />
I am planning on outlining how to do this in more detail in later posts.<br />
I will also put some things on serverless how-tos as contrast. There is good stuff there so an eye should be kept on it.<br />
<br />
Cheers,<br />
B.<br />
<br />
<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-21968727018822314022016-04-14T11:30:00.003-04:002016-04-14T11:30:45.100-04:00node: April 2016 Charlotte Node.js User Group materialFolks please find the links to the presentation materials and code samples from our April 2016 Node.js meeting. I hope this will get you kickstarted with node:<br />
<br />
<a href="http://www.boncode.net/downloads/CharlotteNodeJSApril2016.pdf" target="_blank">The presentation slides</a><br />
<a href="http://www.boncode.net/downloads/CodeSamplesApril2016.pdf" target="_blank">The sample code</a><br />
<br />
Here again the objectives from out meetup "<b>Kicking it with Node, starter edition</b>" :<br />
By popular demand we will focus our sessions on how the node ecosystem works. This will include the setup of the environment the idiosyncrasies of JavaScript and base understanding of the event loop and asynchronicity.<br />
<br />
So in the next session we will look at some of this:<br />
<br />
<ul>
<li>installing Node.js </li>
<li>understanding async tasks </li>
<li>understanding the event loop </li>
<li>what are callbacks </li>
<li>creating projects </li>
<li>understanding node modules and packages </li>
<li>organizing code </li>
<li>writing your own module </li>
<li>what is the package.json file </li>
<li>managing 3rd party packages with npm </li>
<li>the require system </li>
<li>exploring core module examples in node</li>
</ul>
<br />
Cheers,<br />
B.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-91317758816272229452015-11-11T15:06:00.000-05:002015-11-11T15:06:27.868-05:00node: Easier debugging of nodeunit with node-inspector on WindowsI was exploring how to get insight into a piece of JS code running on NodeJS. All the things were there but for some reason the unit tests were not behaving correctly.
To debug this using node-inspector seem to be the logical choice but I could not find an easy way avenue to get things started.<br />
<br />
I would normally start my unit test pointing nodeunit to a directory using the windows command that becomes available after installing nodeunit via npm globally:<br />
<br />
<span style="background-color: #f0f0f0; font-family: monospace , sans-serif; font-size: 16px; letter-spacing: -0.48px; line-height: 25px; white-space: pre;">c:\> nodeunit /test</span><br />
<br />
This would run through all the unit tests in the /test directory. I wanted a similar mechanism when I needed to run debugging. Googling things was not very helpful as all the examples I found were OSX or Linux specific. However, the solution in the end was fairly simple.<br />
<br />
Here are the steps from the beginning:<br />
<br />
a) install node-inspector globally<br />
<br />
<span style="background-color: #f0f0f0; font-family: monospace , sans-serif; font-size: 16px; letter-spacing: -0.48px; line-height: 25px; white-space: pre;">c:\> npm install -g node-inspector</span><br />
<br />
b) install nodeunit globally<br />
<br />
<span style="background-color: #f0f0f0; font-family: monospace , sans-serif; font-size: 16px; letter-spacing: -0.48px; line-height: 25px; white-space: pre;">c:\> npm install -g nodeunit</span><br />
<br />
c) locate the nodeunit command file normally somewhere like C:\Users\[logged in user]\AppData\Roaming\npm\nodeunit.cmd. Or you can use the "<span style="font-family: "courier new" , "courier" , monospace;">where</span>" command like so:<br />
<br />
<span style="background-color: #f0f0f0; font-family: monospace , sans-serif; font-size: 16px; letter-spacing: -0.48px; line-height: 25px; white-space: pre;">c:\> where nodeunit</span><br />
<br />
d) use a text editor like notepad to open the command file<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUaJ8G2VwvuhbLNxJIh2yRgSspf2RdbJB63UQj-bAxBlKgQELdojZWz8dkI_ZS8uBgBz67S_uvUP2GLDRYiQSD6HI27_Hed68xJmZKsNF-fBx0J6EnVyMN8Kd_ll_TB9vdBzBdR00S1-k/s1600/nodeunitcmd.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="248" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUaJ8G2VwvuhbLNxJIh2yRgSspf2RdbJB63UQj-bAxBlKgQELdojZWz8dkI_ZS8uBgBz67S_uvUP2GLDRYiQSD6HI27_Hed68xJmZKsNF-fBx0J6EnVyMN8Kd_ll_TB9vdBzBdR00S1-k/s400/nodeunitcmd.png" width="400" /></a></div>
<br />
<br />
e) change the nodeunit command file by adding the debug flags (--debug-brk) and save as <b>nodeunit-debug.cmd</b>. Here is the content of the fully changed command file:<br />
<br />
<span style="background-color: #f3f3f3; font-family: "courier new" , "courier" , monospace;">@IF EXIST "%~dp0\node.exe" (</span><br />
<span style="background-color: #f3f3f3; font-family: "courier new" , "courier" , monospace;"> "%~dp0\node.exe" "--debug-brk %~dp0\node_modules\nodeunit\bin\nodeunit" %*</span><br />
<span style="background-color: #f3f3f3; font-family: "courier new" , "courier" , monospace;">) ELSE (</span><br />
<span style="background-color: #f3f3f3; font-family: "courier new" , "courier" , monospace;"> @SETLOCAL</span><br />
<span style="background-color: #f3f3f3; font-family: "courier new" , "courier" , monospace;"> @SET PATHEXT=%PATHEXT:;.JS;=;%</span><br />
<span style="background-color: #f3f3f3; font-family: "courier new" , "courier" , monospace;"> node --debug-brk "%~dp0\node_modules\nodeunit\bin\nodeunit" %*</span><br />
<span style="background-color: #f3f3f3; font-family: "courier new" , "courier" , monospace;">)</span><br />
<br />
<br />
You are done with your setup config. Thereafter you only need to use the new debug command when you want to debug test cases.<br />
<br />
a) run your tests with new <span style="font-family: "courier new" , "courier" , monospace;">nodeunit-debug</span> command you just created. You should, of course, do so in the directory of your application rather than in the root of the drive. Assuming that all your unit tests are under a /test subdirectory you could do it like so:<br />
<br />
<span style="background-color: #f0f0f0; font-family: monospace , sans-serif; font-size: 16px; letter-spacing: -0.48px; line-height: 25px; white-space: pre;">c:\[app dir]> nodeunit-debug /test</span><br />
<br />
b) in second command window run node inspector instance and inspect code in browser (Chrome)<br />
<br />
<span style="background-color: #f0f0f0; font-family: monospace , sans-serif; font-size: 16px; letter-spacing: -0.48px; line-height: 25px; white-space: pre;">c:\> node-inspector</span><br />
<br />
c) open chrome to debug your code (http://127.0.0.1:8080/?ws=127.0.0.1:8080&port=5858)<br />
<br />
Here is an image of a,b, and c steps in action:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3ofdZijC2labd7rpvf1LLj0Jgb8KiXIoMohjDTRD_9wL9fg5vW1f31DohxXeImxbJ3-vg3LRzs4j991q7aBgxs9Z51UIN_xPnWvZFilrPpRHpyIurVUFBE79G5irVRYF4pjV7Wy5uiqI/s1600/debugrunning.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="301" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3ofdZijC2labd7rpvf1LLj0Jgb8KiXIoMohjDTRD_9wL9fg5vW1f31DohxXeImxbJ3-vg3LRzs4j991q7aBgxs9Z51UIN_xPnWvZFilrPpRHpyIurVUFBE79G5irVRYF4pjV7Wy5uiqI/s640/debugrunning.png" width="640" /></a></div>
<br />
<br />
That is it.<br />
<br />
Cheers,<br />
B.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-17900123086968744582015-07-15T16:35:00.003-04:002015-07-17T13:27:47.759-04:00JQM: A mini MVC Application Structure using jQuery Mobile and RequireJS<h3>
The Rational</h3>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdBZZks8bzepodO29A76t8EJ4kgzWg5pEfmT8G4yZIC7u1eRLHmtfsdRbNnycRMEsCpHH5oIDXAoA0C305f6-iK3mcJ8rnyFVuYdjb63ERtd_T8rI3J-PFs1AnN4_ty968qLRlrqYmKRw/s1600/SimpleSite.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdBZZks8bzepodO29A76t8EJ4kgzWg5pEfmT8G4yZIC7u1eRLHmtfsdRbNnycRMEsCpHH5oIDXAoA0C305f6-iK3mcJ8rnyFVuYdjb63ERtd_T8rI3J-PFs1AnN4_ty968qLRlrqYmKRw/s400/SimpleSite.png" width="190" /></a>When it comes to the power jQuery Mobile (jqm) to help us organize our code we could pretty much say it in one hyphened word: "non-existent".<br />
How could this pass with such a popular library. Well, the simple answer is that it is by design. This does not mean, however, that you should not organize your jqm code projects. You are free to use any JavaScript organizing principle or helper library, e.g. <a href="http://backbonejs.org/" target="_blank">BackBone </a>or <a href="http://emberjs.com/" target="_blank">Ember</a>, you like. Jqm's focus is the page paradigm and rendering of touch friendly UI.<br />
<br />
This, of course, does mean that you have your work cut out for you to make decisions surrounding your project. Looking at very common pattern of jqm apps, they tend to be an amalgamation of different libraries that have many layers of code and logic that needs to be organized. And, needless to say, there is always seems to be a little bit of overlap in library's coverage, e.g. if you used Backbone for route handling, we will need to turn off the native jqm methodology etc.<br />
<br />
In the particular approach I am outlining in this post, the goal was to use minimal amount of libraries while still creating convention based organization of code, ui, and data. We should be able to split our program into distinct parts that the browser can load when needed.<br />
This will help us in following manner:<br />
<br />
<ol>
<li>decoupling of presentation and logic</li>
<li>modular JavaScript code</li>
<li>decoupling of the page paradigm from singular page apps</li>
<li>organization of code by convention</li>
</ol>
<br />
<br />
Overall, the maintenance of our program should be easier, while adding new parts becomes child-play ;o)<br />
<br />
After a little experimentation, I decided the only thing needed was a little extra JavaScript and the <a href="http://requirejs.org/" target="_blank">RequireJS</a> library. Let me explain how.<br />
<br />
<br />
<h3>
The Setup</h3>
Standard application initialization occurs through the <b>index.html </b>page. However, unlike most JS apps there is only one <script> tag and that tag loads RequireJS. Thereafter the application parts are either loaded by RequireJS or JQM page loader. Thus, the script tag jungle is avoided. Clean abstractions of dependencies and libraries are all captured in the RequireJS config file.<br />
<br />
Since this is a sample app it makes liberal use of <span style="font-family: Courier New, Courier, monospace;">console.log</span> function.<br />
<br />
<pre class="brush:js"> <script data-main="app/config" src="libs/require.js"></script>
</pre>
<br />
Also something that is different here is the externalized page header. I did not add an externalized page footer which can be easily done but was not needed for my example. An externalized header can easily be repeated on each subsequently loaded jqm page and thus we can focus on the page content. We will still change the header display dynamically and add proper navigation as we move between pages.<br />
<br />
<pre class="brush:js"><!-- external header used on all pages we will hide buttons dynamically -->
<header data-theme="b" data-role="header" data-title="Simple App" data-position="fixed">
<a id="btnHome" data-rel="back" data-transition="slide"
class="ui-btn ui-shadow ui-corner-all ui-icon-arrow-l ui-btn-icon-notext ui-btn-inline"
title="move back">back</a>
<h1></h1>
<a href="#info" id="btnInfo"
class="ui-btn-right ui-btn ui-btn-inline ui-btn-icon-notext ui-mini ui-corner-all ui-icon-info"
title="">Info</a>
</header>
</pre>
<br />
<br />
<h3>
The Application Structure</h3>
The application is structured in such a fashion that a certain convention can be observed.<br />
The main directories under /app are<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOCNrnhkvc8DiQDEGiXKhYcG-ZdMxLLqI1tGcR_TxAp1Rb0NBGu1sNvFNZglxx1Vm-hScTrGDtDyZj8lCHklii0T-oXNIhU7BveLLXq9kjSoaK1wRpUheUSgPbRPfEBziSoQ5DrsJG3LU/s1600/ApplicationStructure.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOCNrnhkvc8DiQDEGiXKhYcG-ZdMxLLqI1tGcR_TxAp1Rb0NBGu1sNvFNZglxx1Vm-hScTrGDtDyZj8lCHklii0T-oXNIhU7BveLLXq9kjSoaK1wRpUheUSgPbRPfEBziSoQ5DrsJG3LU/s400/ApplicationStructure.png" width="192" /></a></div>
<br />
<ul>
<li>controllers</li>
<li>data</li>
<li>pages</li>
<li>services</li>
<li>stores</li>
</ul>
<br />
<br />
<br />
All code files except the RequireJS config file are in a slightly modified JavaScript <a href="https://github.com/amdjs/amdjs-api/wiki/AMD" target="_blank">AMD Module format</a>.We are also caching jqm views (pages) once they have been loaded using the jqm domCache directive:<br />
<span style="font-family: Courier New, Courier, monospace;">$.mobile.page.prototype.options.domCache = true;</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<br />
<h4>
controllers</h4>
This is where we place all controller logic for our views. I maintained a convention where views do not have any logic or binding and minimal links. Controllers are automatically loaded and initialized based on the view (page) that is being requested. The app will check for a similarly named controller and start the process. Thus, all page behavior, event binding, business logic would be handled here.<br />
<br />
If the controller exports (or exposes) a function named "init", that function will be called after each page load or display.<br />
<br />
The overall convention for the controller loading process and application behavior are coded into <span style="font-family: Courier New, Courier, monospace;">start.js</span> module. It acts as the overall controller for the application.<br />
<br />
Here is the basic controller construct example.<br />
<br />
<pre class="brush:js">//controller sample
define(
function() {
console.log("empty controller loaded");
//public (export)
return {
init: function(){
console.log("init was called")
}
}
});
</pre>
<br />
<h4>
data</h4>
I have placed a file containing a json array of objects into this file. Thus, we can refer to this as our "database". This is not a convention that is enforced on the code layer. I chose to abstract data in this fashion. It could easily be extended to be the connection layer to a database API etc.<br />
<br />
<h4>
pages</h4>
The pages in our app are the view of the MVC model. I chose to use very simple views. These are snippets of HTML just with the JQM markup needed to render a basic skeleton. Logic that changes the view is either in controller or services layer.<br />
<br />
Here is an example of a view. It is just the date-role="page" part of the jqm html markup.<br />
<br />
<pre class="brush:js"><section data-role="page" id="composerDetail" data-title="Composer Detail">
<div role="main" class="ui-content">
<h2 id="composerName">Composer Name</h2>
<hr>
<!-- composer info -->
<div class="ui-grid-a ui-responsive" >
<div class="ui-block-a center" style="width:20%;">
<div class="">
<img id="composerImage" src="" alt="composer image" style="vertical-align: middle;">
</div>
</div>
<div class="ui-block-b" style="width:80%; vertical-align: top;">
<div class="ui-body ui-body-d"><p id="composerDescription">composer information</p></div>
</div>
</div>
<!-- buttons -->
<div class="ui-grid-a center" >
<div class="ui-block-a" style="width:20%;">
<div class="ui-body ui-body-d"><a data-rel="back" data-transition="slide" class="ui-btn ui-shadow ui-corner-all ui-icon-arrow-l ui-btn-icon-notext ui-btn-inline" title="Back to composer list">back to list of composers</a></div>
</div>
<div class="ui-block-b" style="width:80%;">
<div class="ui-body ui-body-d">
<button id="btnWiki" class="ui-btn ui-icon-arrow-u-r ui-btn-icon-right ui-corner-all" title="More information on WikiPedia" style="width:80%">WikiPedia</button>
</div>
</div>
</div>
</div>
</section>
</pre>
<br />
<br />
<h4>
services</h4>
This is where I paced example services that can be used in other modules. In my case just a way to abstract jquery ajax calls and handle generic responses. But, this could be easily expanded to anything that is shareable across the modules or detailed implementation that would be make controllers large and unwieldy. This is not a enforced by code but a convention I am suggesting. It helps to separate heavy logic into distinct modules for maintainability.<br />
<br />
<h4>
stores</h4>
I used the stores to abstract interaction with the data layer. For example my sample <span style="font-family: Courier New, Courier, monospace;">ComposersDataStore.js</span> in this project can sort the composers, return a specific one etc. This is also not enforced by code, but rather a convention I am proposing for your app build.<br />
<br />
<h3>
The Init Process</h3>
After loading of the index.html a list of dominoes begins to fall<br />
<br />
<ol>
<li>Require will load <span style="font-family: Courier New, Courier, monospace;">config.js</span> which contains the environment definition and load dependencies. </li>
<li><span style="font-family: Courier New, Courier, monospace;">config.js</span> will, in turn, load the <span style="font-family: Courier New, Courier, monospace;">start.js</span> module which contains our overall application controller</li>
<li><span style="font-family: Courier New, Courier, monospace;">config.js</span> will also switch to the "main" page (view) which will trigger common actions by the overall application controller as defned in <span style="font-family: Courier New, Courier, monospace;">start.js</span>: </li>
<ol>
<li>Load the main view (<span style="font-family: Courier New, Courier, monospace;">main.htm</span>)</li>
<li>Load the <span style="font-family: Courier New, Courier, monospace;">main_controll.js</span> controller</li>
<li>Call the init function of main controller</li>
</ol>
</ol>
<br />
Thereafter it is up to the user and his/her interactions which pages will be loaded and which actions will be triggered.<br />
<br />
Unfortunately there seems to be a bug in the jQuery Mobile page loading (page widget) which makes initialization phase inconsistent. The process is not always kicked of correctly so it required for me to do a check in the index.html itself. If we did not find the main controller module loaded after 5 seconds, we would switch to the main view one more time, which triggers the part 3 and seems to fix things. This loaded the app consistently across all devices.<br />
<br />
<pre class="brush: js"> setTimeout(function(){
if (!require.defined("controllers/main_controll")) {
console.log("catch all triggered.");
$( ":mobile-pagecontainer" ).pagecontainer( "change", "app/pages/main.htm", { showLoadMsg: true } );
}
},5000)
</pre>
<br />
<h3>
</h3>
<h3>
The Hook</h3>
<div>
The element that drives the main convention of this app is implemented in the pagecontainerchange event hook. This event is exposed by jQuery mobile.</div>
<div>
<span style="font-family: 'Courier New', Courier, monospace;"><br /></span></div>
<div>
<span style="font-family: 'Courier New', Courier, monospace;">$(document ).on( "pagecontainerchange", function() {}</span></div>
<div>
<br /></div>
<div>
Here we automatically load the appropriate controller based on the id of the view (page) and initialize and call the init() function of the module. In addition, we change display name of the view based on what is in the data-title of the jqm view definition. All this happens in the <span style="font-family: Courier New, Courier, monospace;">start.js</span> application controller. </div>
<div>
<br /></div>
<div>
Of course, in a more opinionated implementation, additional conventions could be coded here; for example, automatic loading of data-stores and even binding of the store's fields to form elements. Thus, the concept can be expanded. Experiment and see if you could add to it.</div>
<div>
<br /></div>
<div>
<br /></div>
<h3>
GitHub baby</h3>
The complete project can be reviewed or downloaded from GitHub. I have included the libraries and versions of jQuery and RequireJS so things should be able to work out of the box:<br />
<br />
Expanded Project for <a href="https://github.com/Bilal-S/jqmSimpleMVC" target="_blank">Download from GitHub</a><br />
<br />
Enjoy,<br />
B.<br />
<br />
<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-23588936629627911522015-05-23T18:22:00.001-04:002015-06-01T14:49:04.773-04:00App Idea: CircleDJ<h2>
The Idea</h2>
<br />
So you have music you are listening to, you like a piece of music so much you take out your ear-buds and press them on your friends to listen to the part of the song that you like so much. You guys start chatting about how cool the song is and how some other band is equally cool. You go back and forth pulling things from your individual play lists and having fun chatting about music and friendship.<br />
<br />
The way you go about it seems rather old-school in the age of mobile tech, doesn't it? What if you had each an app loaded that could make this sharing easier, better and even more fun? What if you could comment on parts of pieces of the song as it was being played? Even bring in other friends into a "circle" to listen to the same song as it is being played. No more sharing of ear-buds while being able to comment and recommend a dynamic playlist? Everyone in the "circle" could assume DJ duties, putting things on a queue or taking over playback directly?<br />
<br />
Initial idea assumes that songs are owned outright by all participants, but alternatives are possible where this is build to work on top of premium subscription services that give people already access to all songs in catalog.<br />
<h3>
More Features</h3>
<br />
<ul>
<li>Users can invite friends into special purpose circles, e.g. School, Workout etc. </li>
<li>You can see who is being DJ in which circle</li>
<li>Listening and comment history is available for circle members</li>
<li>Circle members can compare their song libaries</li>
<li>Allow in-app purchases of "missing" songs</li>
<li>Record snippets with comments to post on twitter or Vine</li>
</ul>
<h3>
Techno Mumbo Jumbo</h3>
<div>
This is the section I am trying to sound edumacated.</div>
<div>
<ul>
<li>The basic principle is based on synchronized playback (stream or local) of media (audio or video) with shared playback control.</li>
<li>Dymanic content and control of media, e.g. different users can control speed, and location of playback and this can by dynamically changed either via an election or granting scheme.</li>
<li><br /></li>
</ul>
</div>
<br />
<h3>
Extensions</h3>
<br />
<ul>
<li>Build on a music streaming service, e.g. Pandora or Spotify</li>
<li>This idea of "social-sharing" and "active" commenting can be extended to field of any stream-able media, e.g.Video. If you are wanting to do similar App on the basis of YouTube or other video streaming services.</li>
<li>Bots could partake in playback control to make recommendation to groups and playback parts or tracks.</li>
<li>The potential exists to "redefine" radio as we know it in this format. "Radio" users could take alternate control and "DJ" or curate for specific group or at specific times for other listeners. </li>
<li>Feedback loop and control look can be build via twitter like services. If enough of a hastag is tweeted alongside another song specific hashtag, a bot can put the most on demand songs into the play-queue.</li>
</ul>
<br />
<h3>
Social Implications</h3>
<br />
<ul>
<li>Circle songs and comments can be shared on twitter, facebook, etc.</li>
<li>Big Data: comments can be analyzed for trends (frequency language, power parts in songs etc.)</li>
<li>Song Ratings for activity types</li>
<li>Extract snippets to post with comments</li>
</ul>
<br />
<h3>
Related Competitive Ideas</h3>
<br />
<ul>
<li>It would be expected that streaming services will build something like this into their apps to ensure higher level of stickiness for their premium services. This could be only available for premium subscribers, inviting others to their listening circle would require subscription. Or, in a modified form without the subscription you cannot participate actively (no commenting, no DJing)</li>
</ul>
<br />
<br />
<h3>
Monetization</h3>
<br />
<ul>
<li>The assumption here is that everyone owns a copy of the song, if not friend that wish to listen in will have to purchase the song. An affiliate payment system can be used or direct resale of music.</li>
<li>Recomendation engines to build suggestion to the circle members for purchase.</li>
<li>Advertising (I do not like to mention this since this seems to be overused)</li>
<li>Subscription element to unlock features, e.g. ability to DJ for the circle</li>
<li>In app purchase of songs</li>
<li>DJs could be hired to remote play and interact</li>
</ul>
<br />
<br />
<h3>
What are these post about</h3>
So, like seemingly, everyone I have been collecting ideas about apps that would be "next big thing" and promptly putting them on a shelf based on time and resource demands. I am fleshing things out in bullet points. Rough bullet points! You are free to do with it whatever tickles your fancy.<br />
<br />
I have decided to break with the cycle of selfishness and just post the ideas for anyone to use. Though I am not opposed to gazillions and instant nerd-fame, I have come to the conclusion that I rather have the idea be turned into action when I know that I will not be able to.<br />
<br />
Also I am sharing in the hopes that, even, if marginally, I am preempting the people that like to patent just about everything including how my shoe laces tie together from stifling innovation. This is prior art people. Booyah!<br />
<br />
<h4>
Do you expect a return?</h4>
In one word, yes! I am hoping that I get attribution credit or free coffee for life whenever one of these makes it big. You don't even have to tell me, just send me my platinum Starbucks card!<br />
<br />
<h4>
Fool, this already exists!</h4>
If such an idea has already been turned into reality, the world is a better place ! Share it in the comment section. I do not do exhaustive research though I try to look through the app stores trying to find apps with these features. If you think my ideas are a bunch of doodoo, no need to comment, just create better ideas and make the world an even better place.<br />
<br />
<br />
Cheers,<br />
B.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-91663678533531491972014-11-13T12:33:00.003-05:002014-11-13T12:33:43.614-05:00CF: Allowing different extensions for scheduled task log filesThis is a quick post for people that run into this dilemma where there scheduled task stop working upon upgrade. They may receive this type of error in their browser:<br />
<br />
<div class="MsoPlainText">
<b>Initialization Error: Valid extensions are : log,txt. -
Invalid extension of the file name.</b><o:p></o:p></div>
<br />
At first you go "What the Heck!". Then, look through gazillion lines of code to find where we could have produced this error and could not pin-point it.<br />
Then, more digging to find the culprit:<br />
With the advent of ColdFusion 10 & 11 and the exposure of the scheduled task vulnerability there was a change to what extensions where supported for your log files when you schedule tasks. As a security element these files can only be log and txt files.<br />
<br />
However, the raw capture of the task run is normally neither, more often than not, especially with debug for the local IP enabled the output is HTML.<br />
Thus, we have, now, for many years, used htm as the preferred extension, since the raw capture of the task run is HTML, thus, easy to view in the browser. Forcing it into txt would only makes us rename the file before opening in the browser again.<br />
<br />
Nightmares of unneeded code change ensued...<br />
<br />
Fortunately, the solution seems simple enough.<br />
a) Stop the ColdFusion instance<br />
b) Find the neo-cron.xml<br />
c) find the line that read like <string>txt,log</string><br />
d) add your extension to that line, e.g. <string>txt,log,htm</string><br />
e) start instance<br />
<br />
Hope this helps others who give themselves the "Duh" slap.<br />
<br />
Cheers,<br />
B.<br />
<br />
<br />Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-8688578682638853763.post-35415462184389783262014-06-06T17:21:00.000-04:002014-06-06T17:21:40.219-04:00CF: CCFML or Making the Case for a Different CFML FutureLooking at the demands of our enterprise and the product road-maps as far as they have been disclosed by either Railo or Adobe we discovered a gap between what we are trying to achieve and what the technology is going to offer.<br />
<br />
So, I have taken upon me to summarize a few thoughts and suggestions that I would like to share to see whether we can sway the Railo/Adobe general product direction.<br />
<br />
I believe that focusing ever more innovation resources on the concept of RAD (Rapid Application Development) and language improvements, though interesting and useful, are not making CFML standout sufficiently to make a long-term difference and detract people from leaving or encouraging new people to join.<br />
<br />
In my opinion, the next level of server improvements need to be substantially different from other offerings and, this, in turn, requires a rethinking of what Railo/ACF offerings are.<br />
Specifically,<br />
•<span class="Apple-tab-span" style="white-space: pre;"> </span>Abandonment of the concept of Application Servers<br />
•<span class="Apple-tab-span" style="white-space: pre;"> </span>Remove the need for download/installs<br />
•<span class="Apple-tab-span" style="white-space: pre;"> </span>Remove the need for server administration and management<br />
•<span class="Apple-tab-span" style="white-space: pre;"> </span>Redefine offering as packaged “infrastructure” with smart policy and deployment<br />
•<span class="Apple-tab-span" style="white-space: pre;"> </span>Use clear convention based guidelines for subsystems (cache, application, storage, db)<br />
<br />
Ok, now you say, what the heck are you dreaming about?<br />
Good question, that.<br />
<br />
In short, I am proposing that we work towards a true <b>Cloud CFML</b> platform === <b>CCFML</b>.<br />
<br />
Let me explain:<br />
<br />
We come from a heritage of dealing with individual servers and have embraced that concept wholeheartedly for many years. However, in the age of the cloud we should question those expectations.<br />
How much more attractive could CCFML be when you only need a github/subversion account to deploy your code and a few policy definitions on how large you want your Application to scale and how fast.<br />
When there are application problems, you can define policies how much CPU a process may consume, add more CPU cycles specifically for it, and/or cache. All automatically.<br />
You application works automatically, fully scalable, across the globe on CFML.<br />
You are kept abreast of any problems, bottlenecks and are given options to upscale CPU, refactor code, or add instances. But, best of all, you let the Cloud CFML take care of everything that a global CFML service should.<br />
You want to push a new version, just change code and click deploy/schedule button.<br />
<br />
Whatever decisions need to be made to get us this platform nirvana should be in the forefront of cfml future enhancements. This would require more standardization of convention so that we can have auto- configuration over coding whenever possible.<br />
Behavior for deployment will need to be defined and “standard conventions” documented.<br />
Here is just a selection of things that need to be answered on the way towards such a CCFML platform:<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>Which distributed cache to implement and deploy<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>Deployment system magic (version, install, upgrade, start, stop, pause engines)<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>How do you scale session across servers<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>How to communicate among cluster members<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>How do you join servers to clusters<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>How do you create a unified lightweight Application scope across the cluster,<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>How to measure and set time,<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>How to define and use a shared file-system<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>How do you delineate code files storage vs. user assets<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>How do you provide Application policy definitions for scaling<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>How to manage and analyze code performance<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>Create Application Manager (instead of Server Manager)<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>Assigned Server roles, e.g. stateless vs. statefull servers<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>Global logging and analysis<br />
-<span class="Apple-tab-span" style="white-space: pre;"> </span>Etc., etc.<br />
There are probably many elements I don’t quite understand or have not considered. This is where I would ask for the smart people of the community to jump in, but, the point I am trying to emphasize is that future innovation should be directed towards creating such a platform rather than focusing on the minutiae of the language syntax.<br />
<br />
The CFML language is quite mature and tweaks on syntax can only have limited impact on stopping developer attrition while an easy to use application platform has the potential to attract developers.<br />
This model also provides a clear path for providers of this tech to charge for the services with the value recognition they are looking for.<br />
<br />
The good news is that we have many components already; the next step is to create the working package and provide CFML as a first class cloud service.<br />
<br />
We would not be the first, others like Microsoft with Azure and .net and the Java/Scala Play! Frameworks have already started the thought model and are getting traction. I fear that if we hesitate too long we may miss an opportunity to reverse course.<br />
<br />
I hope this is not too confusing of a ramble and I am looking forward to feedback.<br />
<br />
-Bilal<br />
<div>
<br /></div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-41309271394698936652013-10-25T13:51:00.001-04:002013-10-25T13:51:12.628-04:00AWS: Cache Me If You Can! Getting Started with Elasticache.<span style="font-family: Arial, Helvetica, sans-serif;">Here are the <a href="http://boncode.net/downloads/AmazonElasticache.pdf" target="_blank">presentation slides</a> from our monthly Charlotte Cloud Computing Meetup and AWS Charlotte Meetup meeting.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;">Application cache has the potential to tremendously speed up your response times. Putting a cache infrastructure together on the other hand may not be for the faint of heart. </span><br />
<span style="font-family: Arial, Helvetica, sans-serif;">In this meetup we will investigate the use of Amazon ElastiCache. Amazon ElastiCache is a web service that makes it easy to deploy, operate, and scale an in-memory cache in the cloud.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;">Cheers,</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;">B.</span>Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-8688578682638853763.post-37461562220363954602013-06-28T14:36:00.000-04:002013-06-28T14:36:24.062-04:00AWS: Amazon CloudSearch -- Find This!<div class="MsoNormal">
<span style="background: white; color: #555555; font-family: "Verdana","sans-serif"; font-size: 11.5pt; line-height: 115%;">Since we experienced some
issues with this month’s meetup I am publishing the <a href="http://www.boncode.net/downloads/AmazonCloudSearch.pdf" target="_blank"><b>presentation slides</b></a> here.<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="background: white; color: #555555; font-family: "Verdana","sans-serif"; font-size: 11.5pt; line-height: 115%;">We may be able to run
through this at a different time again.<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="background: white; color: #555555; font-family: "Verdana","sans-serif"; font-size: 11.5pt; line-height: 115%;"><br /></span></div>
<div class="MsoNormal">
<span style="background: white; color: #555555; font-family: "Verdana","sans-serif"; font-size: 11.5pt; line-height: 115%;"><u>From our meetup
description:</u><o:p></o:p></span></div>
<br />
<div class="MsoNormal">
<span style="background: white; color: #555555; font-family: "Verdana","sans-serif"; font-size: 11.5pt; line-height: 115%;">So you have used SOLR
and think that is the only way to go for anything search related. But, (isn't
there always a but?) you are tired of maintaining infrastructure or attempting
to scale this thing.<span class="apple-converted-space"> </span></span><span style="color: #555555; font-family: "Verdana","sans-serif"; font-size: 11.5pt; line-height: 115%;"><br />
<span style="background: white;">Well
time to look at alternatives specifically made for the cloud.<span class="apple-converted-space"> </span></span><br />
<span style="background: white;">In
this meetup we will look at AWS search, which is based on A9 search technology
acquired by Amazon. It does all the dirty work for you so you can focus on your
facets ;o)<span class="apple-converted-space"> </span></span><br />
<span style="background: white;">We
will discuss the good and the bad while attempting to use examples and build a
search domain.</span></span><o:p></o:p></div>
<div class="MsoNormal">
<span style="color: #555555; font-family: "Verdana","sans-serif"; font-size: 11.5pt; line-height: 115%;"><span style="background: white;"><br /></span></span></div>
<div class="MsoNormal">
<span style="color: #555555; font-family: "Verdana","sans-serif"; font-size: 11.5pt; line-height: 115%;"><span style="background: white;"><br /></span></span></div>
<div class="MsoNormal">
<span style="color: #555555; font-family: "Verdana","sans-serif"; font-size: 11.5pt; line-height: 115%;"><span style="background: white;">Cheers,</span></span></div>
<div class="MsoNormal">
<span style="color: #555555; font-family: "Verdana","sans-serif"; font-size: 11.5pt; line-height: 115%;"><span style="background: white;">B.</span></span></div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-71285582651302261662013-04-04T10:26:00.001-04:002013-04-04T10:26:03.513-04:00CF: Railo: Using VisualVM tool to monitor running Railo serversI had written a <a href="http://boncode.blogspot.com/2010/04/cf-java-using-free-visualvm-tool-to.html" target="_blank">Adobe ColdFusion specific</a> article on how to use the free <a href="http://visualvm.java.net/" target="_blank">VisualVM </a>tool to get insight into the workings of the Java Virtual Machine. I have since been asked to provide similar guide for <a href="http://getrailo.org/" target="_blank">Railo</a> CFML engine.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPqhWKsYtWR25eC0s4jOn1UXznSrTBPnZPdhDYMDjKTYWRy1OHISqm_qYmh0QM00_zkdd7fyBuSjjISKRvsKs3xWc86zsWVzyJliImnCIcV1au4QTG445v_OJKZ1PM1L1qB-4Bpep-f9w/s1600/Railo_VisualVM_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="472" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPqhWKsYtWR25eC0s4jOn1UXznSrTBPnZPdhDYMDjKTYWRy1OHISqm_qYmh0QM00_zkdd7fyBuSjjISKRvsKs3xWc86zsWVzyJliImnCIcV1au4QTG445v_OJKZ1PM1L1qB-4Bpep-f9w/s640/Railo_VisualVM_5.png" width="640" /></a></div>
<br />
<br />
The good news is that the implementation is very similar and mostly follows the same path. I will demonstrate this using Windows OS example. I assume in this example that you have used the standard Railo installer for windows.<br />
<br />
<br />
If so, here are some simple steps to use this great tool set working with Railo.<br />
<br />
<b>1</b>) download java <b>jdk</b> also referred to as <i>Java SE Development Kit</i>. You can use 1.6.38 or later or 1.7.13 or later. Again, Important to get the JDK not the JRE.<br />
<a href="http://www.oracle.com/technetwork/java/javase/downloads/">http://www.oracle.com/technetwork/java/javase/downloads/</a><br />
<br />
<b>2</b>) Download Visual VM:<br />
<a href="https://visualvm.dev.java.net/">https://visualvm.dev.java.net/</a><br />
<br />
<b>3</b>) Configure Railo jmx access:<br />
On Windows, best way is to go to <u>Tomcat Service Control</u>, open the <u>Java </u>panel and add the following to the Java Options text box: (you can change the ports etc. this is is my sample):<br />
<br />
-Dcom.sun.management.jmxremote.port=8701<br />
-Dcom.sun.management.jmxremote.ssl=false<br />
-Dcom.sun.management.jmxremote.authenticate=false<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHn-kc8258DpexWHuGkOvrjihZsD1fMOj5KRBXyOz7lHeDX0JbQfZcvThn3CfG3gbRiKHtu3jzpLvisqTzEAX0Sl-6199Lfjax2s7ywpWSCuPe1O3S6a6QHjCAj_uJsjva733Nf_W1bco/s1600/Railo_VisualVM_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHn-kc8258DpexWHuGkOvrjihZsD1fMOj5KRBXyOz7lHeDX0JbQfZcvThn3CfG3gbRiKHtu3jzpLvisqTzEAX0Sl-6199Lfjax2s7ywpWSCuPe1O3S6a6QHjCAj_uJsjva733Nf_W1bco/s640/Railo_VisualVM_1.png" width="640" /></a></div>
<br />
<br />
<br />
You can decide whether to use ssl or not, and also on the port to use. If you want to use jmx authentication I would recommend you read:<br />
<a href="http://java.sun.com/javase/6/docs/technotes/guides/management/agent.html#gdenl">http://java.sun.com/javase/6/docs/technotes/guides/management/agent.html#gdenl</a><br />
<br />
You will need to restart your server after you have completed your changes.<br />
<br />
<br />
<b>4</b>) Configure your Visual vm start up to point to your jdk if you have not set environmental variables:<br />
e.g. on Windows<br />
if you extracted the visualvm files into C:\visualvm135<br />
and<br />
Your JDK is located in C:\Java\jdk1.7.0_13<br />
then you can use the following command line:<br />
<br />
C:\Java\visualvm_135\bin\visualvm.exe --jdkhome "C:\Java\jdk1.7.0_13"<br />
<br />
You can also add a batch file shortcut for reuse, e.g.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixLayIEWKDScZAzD4x5Z6RPZVLEsSVZUnkKduvZkJkgwF8dHUe6xuj5F_xfIthLQ1TYsIcdaT3CuGaECgAoIvHjx6CBNf0yySC4Zk8BfvgoAdBEeuDoAg_ZG1-kjrQwFD696f_s9-Lpms/s1600/Railo_VisualVM_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixLayIEWKDScZAzD4x5Z6RPZVLEsSVZUnkKduvZkJkgwF8dHUe6xuj5F_xfIthLQ1TYsIcdaT3CuGaECgAoIvHjx6CBNf0yySC4Zk8BfvgoAdBEeuDoAg_ZG1-kjrQwFD696f_s9-Lpms/s400/Railo_VisualVM_6.png" width="351" /></a></div>
<br />
<b>5</b>) Start up the VisualVM tool (it may have to go through calibration first, simply acknowledge), then, and establish a connection a JMX connection by right clicking on the local node and choosing "Add JMX Connection..."<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhArk05JWaQxKMXfXM-8Q8lJq2ddbH0PmenvcaLtdnLkIx9SWvY5cpNG971j44SttK_3i53uOZhhvMZodOmvCAjUDbBCzciCosLR85PF3jvTSDY0EhUZOtdsJxuZx6eEOQQ6CcPmkZSoc0/s1600/vvm3.GIF" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhArk05JWaQxKMXfXM-8Q8lJq2ddbH0PmenvcaLtdnLkIx9SWvY5cpNG971j44SttK_3i53uOZhhvMZodOmvCAjUDbBCzciCosLR85PF3jvTSDY0EhUZOtdsJxuZx6eEOQQ6CcPmkZSoc0/s1600/vvm3.GIF" /></a></div>
<br />
<br />
<b>6</b>) Add connection parameters:<br />
If the server is local you can use: localhost:[port] in our case: localhost:8701<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9_fcyN_WyUfIAe_-IHkYhrWEzfoSkp-SqJkRY2oDyx1MXOlrjxz5N_naIjOXMAsUDCoDUOTkKBpU9ayG_jJ7cA6aR9CGLKUpz0XSFSd25cHrvGNpbRT5Fg0INeXrezjpSx5Rtpb4nKWY/s1600/Railo_VisualVM_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9_fcyN_WyUfIAe_-IHkYhrWEzfoSkp-SqJkRY2oDyx1MXOlrjxz5N_naIjOXMAsUDCoDUOTkKBpU9ayG_jJ7cA6aR9CGLKUpz0XSFSd25cHrvGNpbRT5Fg0INeXrezjpSx5Rtpb4nKWY/s320/Railo_VisualVM_7.png" width="320" /></a></div>
<br />
<br />
Now you should be able to monitor basic statistics of your Railo environment as it runs, and do some nifty things like forcing garbage collection and dump heap files for later analysis.<br />
<br />
Cheers,<br />
B.<br />
<br />
<br />
Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-8688578682638853763.post-4710956065190573652013-02-11T16:16:00.002-05:002013-02-11T16:16:12.840-05:00CF: cfObjective() 2013... talk is cheap (relatively ;o)<br />
<div style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;">
<img alt="" border="0" height="125" src="http://www.cfobjective.com/cfo/assets/Image/badges/2013/CFObjective_speaker_125x125.png" width="125" />
</div>
I decided to submit my slew of topics to this year's <a href="http://www.cfobjective.com/" target="_blank">cfObjective </a>conference. I had many things that I wanted to investigate or thought were worth sharing... never can pick really...<br />
Fortunately for me the selection committee picked two of my submissions and, thus, if you time it right and have no better place to be, come join me at cfObjective in Minneapolis to dig into the mobile development realm ...<br />
<br />
"Hey Bilal, come to the point", you say. "What are you going to yak about, since I am biZy! With a capital Z".<br />
Well, ok, here are the things I am going to talk about:<br />
<br />
<b>Design MVC Mobile App Visually In Hours</b><br />
<br />
<br />
<blockquote class="tr_bq">
HTML5 / CSS3 / JavaScript Mobile Apps are becoming more popular. Unfortunately, the tools that we use to design them have not changed much. This talk is centered on using more advanced design tool such as Sencha Architect 2 to visually create front-end MVC mobile prototypes quickly in WYSIWYG fashion. We will step through the elements and create our own app and discuss native deployment options as well.<br />
We will learn how to navigate the common UI of Sencha Architect, how to start and structure an Architect project, how to create a common mobile app with navigation pattern , how to link components, how to bind data, how to deploy generated code in your project, the difference in prototyping levels and the fit of rapid prototyping tools such as Architect, and we are going to have fun creating a real working mobile app.</blockquote>
<br />
<b>Mobile but Secure</b><br />
<br />
<br />
<blockquote class="tr_bq">
HTML5 CSS3 applications are becoming increasingly popular for mobile platforms. An assortment of applications makes use of the mobile devices to run, but when it comes to mobile security it is a wild west, the next frontier. What can you do to create mobile or web apps with a more solid security stance and prevent your mobile software from becoming another way to hack into your servers, your customers’ data? If you don't want to be in the news as the next mobile app whose weakness a hacker group used to get sensitive data you should familiarize yourself with the mobile security tips and tricks.<br />
We will take a look at the mobile security top ten and discuss the juiciest elements such as Insecure Data Storage, Weak Server Side Controls, Insufficient Transport Layer Protection, Client Side Injection, Poor Authorization and Authentication and ways and how to mediate them effectively.</blockquote>
<br />
So there you have it.<br />
Hope to see you at the Mall of America in May !<br />
<br />
Cheers,<br />
B.<br />
<br />Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-8688578682638853763.post-57205039465194939772013-01-05T17:25:00.001-05:002013-01-05T17:25:06.169-05:00CF: Scheduled Task Security venerability in Adobe CFGetting hit by a security vulnerability is no fun. This new one using CFIDE scheduling (<a href="http://www.adobe.com/support/security/advisories/apsa13-01.html">http://www.adobe.com/support/security/advisories/apsa13-01.html</a>) seems to have impacted quite a few users.<br />
This all happened around Christmas and went downhill from there most likely automatic network scanning of availability of a certain URL path followed by automated attack.<br />
<br />
<ul>
<li><a href="http://forums.adobe.com/message/4962104">http://forums.adobe.com/message/4962104</a></li>
</ul>
<br />
Charlie Arehart has several blog post on this topic so I am not going to expand too much:<br />
<br />
<ul>
<li><a href="http://www.carehart.org/blog/client/index.cfm/2013/1/2/serious_security_threat">http://www.carehart.org/blog/client/index.cfm/2013/1/2/serious_security_threat</a></li>
<li><a href="http://www.carehart.org/blog/client/index.cfm/2013/1/2/Part2_serious_security_threat">http://www.carehart.org/blog/client/index.cfm/2013/1/2/Part2_serious_security_threat</a></li>
</ul>
<br />
The old adage to lock down your server still holds but is no solace to the people that got hit since it would impact a fully patched server.<br />
The short of this is to disable access to certain CFIDE paths. This may be the practice that you already follow, then kudos!, more importantly, get the word out to other users. Let your friends know so they don't fly blind.<br />
<br />
Overall I am surprised by this vulnerability since it seems to originate from a vector that, in my mind, should require authentication or at least some sort of access control. Seemingly the scheduling of tasks is vulnerable and wide open by default. Initial thought on this: Crap!<br />
Maybe, as a community, we should vet more closely the out of the box CFML code that is being deployed. The secure stance always has been to not deploy any example and docs on production, however, this is part of the system would be required to administer it.<br />
<br />
Couple of things that I would like to delve deeper into:<br />
<br />
<b>a) Detecting Code Compromise:</b><br />
In the last talk that I had presented around application security I had shown an example that can be easily implemented and allows users to detect any code change on the machine.<br />
The idea is to generate an application signature that, then, can be verified to see whether the application is still consistent with what you published.<br />
This is a simple version, you may expand and adjust. This will help you detect whether you have been compromised.<br />
You can do this against the CFIDE, Customtag, and/or any other directory you store code in. You can create separate signatures (i.e. modules) or combined ones.<br />
<br />
The goal is to quickly be able to tell whether you were hit by a zero-day vulnerability or anything else made its way onto your server that you did not expect.<br />
<br />
It involves two general steps:<br />
<br />
First run a recursive cfdirectory on your website/app and, second, create a hash from it.<br />
Something like this:<br />
<br />
<div class="mycode">
<pre><span style="color: blue;"><</span>cfdirectory action<span style="color: blue;">=</span><span style="color: purple;">"LIST"</span> directory<span style="color: blue;">=</span><span style="color: purple;">"c:\inetpub\wwwroot"</span>
name<span style="color: blue;">=</span><span style="color: purple;">"myAppFiles"</span> filter<span style="color: blue;">=</span><span style="color: purple;">"*.cf*"</span> recurse<span style="color: blue;">=</span><span style="color: purple;">"Yes"</span><span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: green;"><i><!--- build md5 hash ---></i></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span>cfset myAppSig <span style="color: blue;">=</span> Hash<span style="color: blue;"><b>(</b></span>SerializeJSON<span style="color: blue;"><b>(</b></span>myAppFiles<span style="color: blue;"><b>)</b></span><span style="color: blue;"><b>,</b></span><span style="color: purple;">"MD5"</span><span style="color: blue;"><b>,</b></span><span style="color: purple;">"us-ascii"</span><span style="color: blue;"><b>)</b></span><span style="color: blue;">></span></pre>
</div>
<br />
<br />
Then compare the generated application signature against a last known good one. This can be done on App start, or a scheduled basis, even add hock if you app is small.<br />
<br />
Here is the similar sample that stops application execution if compromised if you include it in your OnApplicationStart function:<br />
<br />
<div class="mycode">
<pre><span style="color: blue;"><</span>cffunction name<span style="color: blue;">=</span><span style="color: purple;">"OnApplicationStart"</span><span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: green;"><i><!---
This assumes that you have a database with a
table named "appcheck" that has at least two fields
id = auto number/indexed
value = text (20)
The first time the app starts, the valid
application signature will be determined.
There after the application will abort if
the signature does not match.
To publish new code.
Clear the appcheck table or insert
the new valid signature as last row.
---></i></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span>cfquery name<span style="color: blue;">=</span><span style="color: purple;">"selCheck"</span><span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
SELECT value
FROM appcheck
WHERE id = (SELECT Max(ID) FROM AppCheck) OR id=0
</span></span><span style="color: blue;"><</span><span style="color: blue;">/</span>cfquery<span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: green;"><i><!--- determine app check crossum ---></i></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span>cfdirectory name<span style="color: blue;">=</span><span style="color: purple;">"selAppFiles"</span> action<span style="color: blue;">=</span><span style="color: purple;">"list"</span> filter<span style="color: blue;">=</span><span style="color: purple;">"*.cf*"</span>
directory<span style="color: blue;">=</span><span style="color: purple;">"#getComponentPath()#"</span> recurse<span style="color: blue;">=</span><span style="color: purple;">"Yes"</span><span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span>cfset strJson <span style="color: blue;">=</span> SerializeJSOn<span style="color: blue;"><b>(</b></span>selAppFiles<span style="color: blue;"><b>)</b></span><span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span>cfset md5Hash <span style="color: blue;">=</span> Hash<span style="color: blue;"><b>(</b></span>strJson<span style="color: blue;"><b>,</b></span><span style="color: purple;">"MD5"</span><span style="color: blue;"><b>,</b></span><span style="color: purple;">"us-ascii"</span><span style="color: blue;"><b>)</b></span><span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span>cfif selCheck<span style="color: blue;"><b>.</b></span>RecordCount<span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: green;"><i><!--- application compromise check.
We could email someone automatically,
raise alarms, call the cops ---></i></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span>cfif md5Hash neq selCheck<span style="color: blue;"><b>.</b></span>value<span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
Application compromised.</span></span><span style="color: blue;"><</span><span style="color: red;"><b>br</b></span><span style="color: blue;">/</span><span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span>cfoutput<span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
#md5Hash# -- #selCheck.value#
</span></span><span style="color: blue;"><</span><span style="color: blue;">/</span>cfoutput<span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span>cfabort<span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span><span style="color: blue;">/</span>cfif<span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span><span style="color: blue;">/</span>cfif<span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span>cfquery name<span style="color: blue;">=</span><span style="color: purple;">"insCheck"</span><span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
INSERT INTO AppCheck
(VALUE) VALUES ('#md5Hash#')
</span></span><span style="color: blue;"><</span><span style="color: blue;">/</span>cfquery<span style="color: blue;">></span><span class="cch1"><span style="color: grey;">
</span></span><span style="color: blue;"><</span><span style="color: blue;">/</span>cffunction<span style="color: blue;">></span></pre>
</div>
<br />
<br />
<br />
<b>b) Preventing people from using the vulnerability:</b><br />
Charlie and other have done a good job of summarizing the source and ways you can prevent the exploit. It all seems to boil down to not let users hit certain CFIDE paths:<br />
<br />
<br />
<ul>
<li>/CFIDE/administrator</li>
<li>/CFIDE/adminapi</li>
<li>/CFIDE/componentutils</li>
</ul>
<br />
<br />
The bad thing is that using IIS/Apache facilities to lock out call URLs may not be good enough. There is the question of virtual paths and ColdFusion built in webservers that can bypass your effort. In addition, the normal Adobe CF connection between IIS and CF is using a wildcard based handling of all request. That means all requests to your website are first routed to CF/connector, even, if you ask for an image. If you know me you know I am biased, but obviously this blows big time, since it causes unnecessary processing on IIS and CF. Try to pause or stop CF and call a static HTML page on IIS or Apache, you will wait for a while. Enough of the ranting! In short, please test your lockout configs after you make changes to make sure that everything is locked out as it should be.<br />
<br />
If you are using ColdFusion 10 on IIS you also have the option to deploy my <a href="http://tomcatiis.riaforge.org/" target="_blank">BonCode </a>connector. It does not block static page access, or hinder non CF processing. Since its first version it had the ability to block access to administration pages; you will not be able to bypass this even if you have the built in CF webserver running (yes, you can still hit the built in webserver if you bypass IIS altogether but that would require deliberate foolishness where you have opened the non-standard port on your Windows OS and firewalls everywhere).<br />
<br />
Here is a blog post on how this feature is used to secure <a href="http://utdream.org/post.cfm/how-to-block-access-to-railo-3-4-administrators-in-iis-7-security" target="_blank">Railo administration pages</a>. This method also works for <a href="http://openbd.org/" target="_blank">OpenBD</a>, and <a href="http://www.adobe.com/products/coldfusion-family.html" target="_blank">Adobe CF10</a>.<br />
The <a href="http://boncode.blogspot.com/2012/06/cf-coldfusion-10-experimenting-with.html" target="_blank">installation for CF10</a> is not out of the box, if there is interest in this I will provide it in later versions. Also this is not officially supported by Adobe, though given the system instability issues and problems that have occurred with the supplied connector you may want to consider testing anyway.<br />
<br />
Cheers,<br />
B.<br />
<br />Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-8688578682638853763.post-80860448332137930442012-11-25T15:02:00.000-05:002012-11-25T15:07:25.283-05:00Reminiscing the difficulties of predicting the future or how the iPhone 5 made it all come trueA few year back, at the height of the iPod boom, I predicted its swift demise.<br />
I might have titled this said blog post something like <a href="http://boncode.blogspot.com/2008/06/why-ipod-must-die.html" target="_blank">Why the iPod must die</a>!<br />
<br />
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:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://upload.wikimedia.org/wikipedia/commons/3/34/Ipod_sales_per_quarter.svg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="540" src="http://upload.wikimedia.org/wikipedia/commons/3/34/Ipod_sales_per_quarter.svg" width="640" /></a></div>
<br />
<br />
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.<br />
<br />
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!).<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
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 !<br />
<br />
Cheers,<br />
B.<br />
<br />
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-22475860399642708262012-11-20T17:44:00.001-05:002012-11-20T17:44:41.088-05:00CF: ColdFusion with Amazon Load Balancer (ELB) and mysterious line breaks causing "unterminated string literal" exceptionsThis seems like a complicated scenario at first until I started thinking about it again.<br />
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.<br />
Then, you spent countless hours going nuts.<br />
<br />
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:<br />
<br />
<br />
<span style="font-family: Courier New, Courier, monospace;"><cfoutput></span><br />
<span style="font-family: Courier New, Courier, monospace;"> <script type="text/javascript"></span><br />
<span style="font-family: Courier New, Courier, monospace;"> var myName = '#fCallForName()#';</span><br />
<span style="font-family: Courier New, Courier, monospace;"> </script></span><br />
<span style="font-family: Courier New, Courier, monospace;"></cfoutput></span><br />
<br />
<br />
The above returned this in browser lets say the name was "John" :<br />
<br />
<br />
<br />
<span style="font-family: Courier New, Courier, monospace;"> <script type="text/javascript"></span><br />
<span style="font-family: Courier New, Courier, monospace;"> var myName = '</span><br />
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span>John';</span><br />
<span style="font-family: Courier New, Courier, monospace;"> </script></span><br />
<br />
<br />
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!<br />
<br />
<br />
<br />
<span style="font-family: Courier New, Courier, monospace;"><cffunction name="fCallForName" returntype="string"></span><br />
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span" style="white-space: pre;"> </span><cfreturn ""></span><br />
<span style="font-family: Courier New, Courier, monospace;"></cffunction></span><br />
<br />
<br />
<br />
<br />
So nope, empty string still produced new line in the JavaScript output. So a few brain cycles go by and<br />
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.<br />
<br />
<br />
<br />
<span style="font-family: Courier New, Courier, monospace;"> <script type="text/javascript"></span><br />
<span style="font-family: Courier New, Courier, monospace;"> var myName = ' ';</span><br />
<span style="font-family: Courier New, Courier, monospace;"> </script></span><br />
<br />
<br />
<br />
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: <a href="http://boncode.blogspot.com/2009/03/cf-coldfusion-functions-and-case-of.html">http://boncode.blogspot.com/2009/03/cf-coldfusion-functions-and-case-of.html</a><br />
... and the fog began to lift.<br />
<br />
I immediately reassigned the function output to a variable like so:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;"><cfset userName = fCallForName()></span><br />
<br />
Then, used the variable to output the content of the function:<br />
<br />
<br />
<br />
<span style="font-family: Courier New, Courier, monospace;"><cfset userName = fCallForName()></span><br />
<span style="font-family: Courier New, Courier, monospace;"><cfoutput></span><br />
<span style="font-family: Courier New, Courier, monospace;"> <script type="text/javascript"></span><br />
<span style="font-family: Courier New, Courier, monospace;"> var myName = '#userName#';</span><br />
<span style="font-family: Courier New, Courier, monospace;"> </script></span><br />
<span style="font-family: Courier New, Courier, monospace;"></cfoutput></span><br />
<br />
<br />
<br />
...and bingo, everything started to work again. No more JavaScript errors. However the conclusions here are more scary then the error:<br />
<br />
a) ColdFusion does introduce more than just a space character when function output is used in place<br />
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.<br />
<br />
Both a) and b) should not happen, but they do ;o(<br />
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 .<br />
<br />
Cheers,<br />
B.<br />
<br />
<br />
<br />
<br />
<br />Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-8688578682638853763.post-33541382755226829782012-10-18T13:47:00.000-04:002012-10-18T13:47:09.005-04:00CF: CFCamp 2012 more of everythingCFCamp 2012 is over. It was another whirlwind affair with more people, more speakers, more topics and more sponsors.<br />
Overall a good gathering that had some nuggets to take away and think about.<br />
Thanks everyone who stayed for my late presentation on practical application security.<br />
<br />
You can <a href="http://www.boncode.net/downloads/PracticalApplicationSecurity2012.pdf" target="_blank">download presentation</a> slides if you want to review them at your leisure.<br />
<br />
Cheers,<br />
B.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-28296972637127353872012-10-12T13:16:00.001-04:002012-10-16T04:24:57.411-04:00CF: CFCamp here we goAnother year, another CFCamp.<br />
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.<br />
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.<br />
In addition there is a smaller CFAcademy segment with expanded hands on training.<br />
If you are participating in Security section of CFAcademy watch this blog as I will post some course materials shortly.<br />
<br />
CFAcademy users please <a href="http://www.boncode.net/downloads/cfacademy2012.zip" target="_blank">download</a> your exercise materials.<br />
You will need :<br />
<br />
<ul>
<li>One of the CFML engines (Adobe, Railo, OpenBD) installed. I will use Railo to work through examples.</li>
<li>A database: MySQL</li>
<li>Create a data source "book1" using the sql dump.</li>
</ul>
<br />
<br />
Cheers,<br />
Bilal<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-13847562140417375302012-09-29T14:11:00.000-04:002012-09-29T14:11:45.133-04:00Sencha: NCDevCon Presentation MaterialsThanks 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.<br />
I am taking a moment to post the materials I used; hope this makes things clear as mud :o)<br />
<br />
Presentation Slides: <a href="http://boncode.net/downloads/MVCinHours.pdf" target="_blank">MVC App in Hours</a> (pdf)<br />
<br />
Step-by-Step: <a href="http://boncode.net/downloads/BuildingMovieFinderArchitect.pdf" target="_blank">Building the Movie Finder App in Sencha Architect</a> (pdf)<br />
<br />
Cheers,<br />
B.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-13653130855759715072012-09-13T15:32:00.001-04:002012-09-13T15:32:22.024-04:00NCDevCon 2012: Regional Developer Conference with focus on Mobile, Web, and ColdFusion<div dir="ltr" style="text-align: left;" trbidi="on">
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTVwebeVrrN-H_LWgb5Ga-3QNKcUwSScR_oz5oJeHv2pOQJvTROXkh9oN0DNU-DKKAdFEIj3c9OJyV9gzYPuueMuxFOBPuRmP6TDn3zw4NPCL7yp9KbxVWzpC9KUAp3YVRIMC92MCmHOw/s1600/NCDevConLogo.gif" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTVwebeVrrN-H_LWgb5Ga-3QNKcUwSScR_oz5oJeHv2pOQJvTROXkh9oN0DNU-DKKAdFEIj3c9OJyV9gzYPuueMuxFOBPuRmP6TDn3zw4NPCL7yp9KbxVWzpC9KUAp3YVRIMC92MCmHOw/s200/NCDevConLogo.gif" width="180" /></a></div>
<div style="margin-bottom: .0001pt; margin: 0in;">
<span style="font-size: 13.5pt;">Ok. Now it has been a cool four years that the Triangle Area
ColdFusion User’s Group (TACFUG) is putting on a conference <a href="http://www.ncdevcon.com/" target="_blank">NCDevCon 2012</a>
(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.<o:p></o:p></span></div>
<div style="margin-bottom: .0001pt; margin: 0in;">
<span style="font-size: 13.5pt;">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.<o:p></o:p></span></div>
<div style="margin-bottom: .0001pt; margin: 0in;">
<br /></div>
<div style="margin-bottom: .0001pt; margin: 0in;">
<span style="font-size: 13.5pt;">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.<o:p></o:p></span></div>
<div style="margin-bottom: .0001pt; margin: 0in;">
<br /></div>
<div style="margin: 0in 0in 0.0001pt;">
<span style="font-size: 13.5pt;">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.<o:p></o:p></span></div>
<div style="margin: 0in 0in 0.0001pt;">
<br /></div>
<div style="margin: 0in 0in 0.0001pt;">
<span style="font-size: 13.5pt;">I have been selected to do<span class="apple-converted-space"> </span>a
presentation on the Mobile side.<span class="apple-converted-space"> </span>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) <o:p></o:p></span></div>
<div style="margin-bottom: .0001pt; margin: 0in;">
<br /></div>
<div style="margin-bottom: .0001pt; margin: 0in;">
<span style="font-size: 13.5pt;">There is still time to book and space available so I hope to connect
with everyone there.<o:p></o:p></span></div>
<div style="margin-bottom: .0001pt; margin: 0in;">
<br /></div>
<div style="margin: 0in 0in 0.0001pt;">
<br /></div>
<div style="margin: 0in 0in 0.0001pt;">
<span style="font-size: 13.5pt;">Cheers,<o:p></o:p></span></div>
<div style="margin: 0in 0in 0.0001pt;">
<span style="font-size: 13.5pt;">-B<o:p></o:p></span></div>
</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-8688578682638853763.post-51848973329611857332012-08-29T15:39:00.002-04:002015-07-17T13:46:04.063-04:00CF: MXunit Automatically Generating Tests and the Challenge of Test Scope<div dir="ltr" style="text-align: left;" trbidi="on">
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.<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Happy Experimenting:<br />
<br />
GenerateTestsPublic.cfm:<br />
<br />
<div class="mycode">
<pre class='brush:cf'>
<!---
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>
</pre>
</div>
<br />
<br />
<br />
<br />
<br />
Cheers,<br />
B.
</div>Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-8688578682638853763.post-9702871660416627192012-07-17T20:29:00.000-04:002012-07-17T20:29:04.889-04:00CF: ColdFusion 10 on Tomcat and the CGI mistery<div dir="ltr" style="text-align: left;" trbidi="on">
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.<br />
<br />
As pointed out previously by myself, <a href="http://blogs.coldfusion.com/post.cfm/what-s-the-deal-with-tomcat-in-coldfusion-10" target="_blank">Rupesh Kumar</a>, 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.<br />
<br />
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.<br />
<br />
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):<br />
<br />
a) Standard Install of CF 10 with Adobe ISAPI connector and IIS.<br />
b) Standard Install of CF 10 with BonCode connector and IIS.<br />
c) Tomcat WAR deployment of CF with BonCode connector and IIS.<br />
d) Tomcat WAR Deployment using Tomcat web server (Coyote)<br />
<br />
I configured the BonCode connector for best alignment with Adobe as outlined in my<a href="http://boncode.blogspot.com/2012/06/cf-coldfusion-10-experimenting-with.html"> previous blog post</a>.<br />
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.<br />
<br />
<br />
I arrived at my conclusions by using a sophisticated setup ;o)<br />
I simply dumped the CGI scope and the count of CGI variables, then compare the dump files. The dump files in the <a href="http://www.boncode.net/downloads/CF10_CGI_Results.zip">download zip document</a> reflect the output for the scenarios above.<br />
<br />
<br />
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.<br />
<br />
The differences in scenario d) relate to the following CGI variables:<br />
Content_Length<br />
Context_Path<br />
Gateway_Interface<br />
HTTPS<br />
<br />
<br />
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.<br />
<br />
Cheers,<br />
B.<br />
<br />
<br />
<br />
<br /></div>Unknownnoreply@blogger.com0