tag:blogger.com,1999:blog-53325820241037501702024-03-25T09:57:34.126-04:00Lee's 2 bytesI'm a "bit-head" so it should be no surprise that I'll be posting on technology, open source, enterprise, Android, Java, Python, Ubuntu - Linux, social media and anything else that catches my attention. I have been a professional software developer for over 18 years and hacked for many years prior to getting paid to play with computers!Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.comBlogger18125tag:blogger.com,1999:blog-5332582024103750170.post-33209472376370836432014-03-19T09:11:00.003-04:002014-03-19T09:11:50.881-04:00RatingBar madness and solution!I've been working with Android recently and I just ran into an irritating "quark" with the RatingBar object and I couldn't find a solution through the usual googling and StackExchange, fortunately I stumbled upon a solution! <br />
<br />
<b>The Problem:</b><br />
The ratingBar has a numStars setting which should indicate the maximum number of stars to display for a rating. In my instance I have this value set to 5, only problem is it displays a many stars and it can possibly fit into the viewable space.. somewhere around 8 stars, uh... hello, I set it to 5! I love it when things are easy!<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-uAhKM8Ke0IY/UymR9wwx0SI/AAAAAAAAF0g/jr1CXERW12c/s1600/Screen+Shot+2014-03-19+at+8.46.19+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-uAhKM8Ke0IY/UymR9wwx0SI/AAAAAAAAF0g/jr1CXERW12c/s1600/Screen+Shot+2014-03-19+at+8.46.19+AM.png" /></a></div>
<br />
This wont do, So here's the code, perfectly reasonable stuff here. (For details on how to do a custom style check out this <a href="http://kozyr.zydako.net/2010/05/23/pretty-ratingbar/">great blog post</a>, no point in me rewriting this one.)<br />
<br />
<div class="p3">
<span class="s4"><ratingbar span=""></ratingbar></span></div>
<div class="p4">
<div class="p1">
<span style="font-family: Courier New, Courier, monospace;"><span class="s1"> </span><RatingBar</span></div>
<div class="p2">
<span class="s2" style="font-family: 'Courier New', Courier, monospace;"> android:id</span><span class="s1" style="font-family: 'Courier New', Courier, monospace;">=</span><span class="s3" style="font-family: 'Courier New', Courier, monospace;">"@+id/rtbDvcMgmt"</span></div>
<div class="p3">
<span style="font-family: 'Courier New', Courier, monospace;"> </span><span style="font-family: 'Courier New', Courier, monospace;"> </span><span style="font-family: Courier New, Courier, monospace;"><span class="s2">style</span><span class="s1">=</span>"@style/CustomRatingBar"</span></div>
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> </span><span style="font-family: Courier New, Courier, monospace;"><span class="s2">android:layout_width</span><span class="s1">=</span><span class="s3">"wrap_content"</span></span></div>
<div class="p2">
<span style="font-family: 'Courier New', Courier, monospace;"> </span><span style="font-family: 'Courier New', Courier, monospace;"> </span><span style="font-family: Courier New, Courier, monospace;"><span class="s2">android:layout_height</span><span class="s1">=</span><span class="s3">"wrap_content"</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="s4"><span class="Apple-tab-span"> </span> </span>android:layout_marginRight<span class="s1">=</span><span class="s3">"25dp"</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="s4"><span class="Apple-tab-span"> </span> </span>android:layout_marginTop<span class="s1">=</span><span class="s3">"5dp"</span></span></div>
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s2">android:max</span><span class="s1">=</span><span class="s3">"5"</span></span></div>
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s2">android:maxHeight</span><span class="s1">=</span><span class="s3">"50dp"</span></span></div>
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s2">android:minHeight</span><span class="s1">=</span><span class="s3">"28dp"</span></span></div>
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s2">android:numStars</span><span class="s1">=</span><span class="s3">"5"</span></span></div>
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s2">android:rating</span><span class="s1">=</span><span class="s3">"0.0"</span></span></div>
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"></span>
</span><br />
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s2">android:stepSize</span><span class="s1">=</span><span class="s3">"1.0"</span> <span class="s5">/></span></span></div>
</div>
<div class="p4">
<span class="s4"><br /></span></div>
<div class="p4">
<span class="s4">This code has nothing wrong with it, so why is it no working as expected? The explanation can be found in the RatingBar source code java doc! </span></div>
<div class="p4">
<span class="s4"><br /></span></div>
<blockquote class="tr_bq">
When using a RatingBar that supports user interaction, placing widgets to the left or right of the RatingBar is discouraged.</blockquote>
<div class="p4">
<span class="s4">What is meant by "discouraged" is it wont work! All sorts of things go wrong, the stars don't fill properly, there are more stars displayed then you requested... Yes don't do this, it doesn't work.</span></div>
<div class="p4">
<span class="s4"><br /></span></div>
<div class="p4">
<span class="s4">The cause in my case was that I was using a TextView next to the RatingBar because that's the requirement! I also had the whole form laid out in a Tablerow which doesn't play nicely with RatingBar either as I discovered when I moved the RatingBar onto its own row. </span></div>
<div class="p4">
<span class="s4"><br /></span></div>
<div class="p4">
<span class="s4">The Solution!</span></div>
<div class="p4">
<span class="s4">The solution is surprisingly simple. The RatingBar works when it's all by itself snuggled up in a Layout with nothing to the right or left. so I wrapped it in its own LinearLayout INSIDE of the TableRow with a TextView right next to it! That did the Trick! </span></div>
<div class="p4">
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-VlLQkBKhStY/UymXfsWRXVI/AAAAAAAAF0w/TGvhWQO2BrA/s1600/Screen+Shot+2014-03-19+at+9.11.05+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-VlLQkBKhStY/UymXfsWRXVI/AAAAAAAAF0w/TGvhWQO2BrA/s1600/Screen+Shot+2014-03-19+at+9.11.05+AM.png" /></a></div>
<br />
<br />
<span style="font-family: Courier New, Courier, monospace;"><TableRow</span></div>
<div class="p2">
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"> </span><span class="s3">android:id</span><span class="s4">=</span>"@+id/tableRowDvcMgmt"</span></div>
<div class="p3">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"> </span>android:layout_width<span class="s4">=</span><span class="s5">"fill_parent"</span></span></div>
<div class="p3">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"> </span>android:layout_height<span class="s4">=</span><span class="s5">"wrap_content"</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"> <span class="s3">android:padding</span><span class="s4">=</span><span class="s5">"5sp"</span> <span class="s1">></span></span></div>
<div class="p5">
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace;"> <span class="s1"><TextView</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"> <span class="s3">android:layout_width</span><span class="s4">=</span><span class="s5">"wrap_content"</span></span></div>
<div class="p3">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"> </span>android:layout_height<span class="s4">=</span><span class="s5">"wrap_content"</span></span></div>
<div class="p3">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"> ...</span> <span class="s1">/></span></span></div>
<div class="p6">
<span style="font-family: Courier New, Courier, monospace;"> </span></div>
<div class="p7">
<span style="font-family: Courier New, Courier, monospace;"><span class="s4"><span class="Apple-tab-span"></span><span class="Apple-tab-span"></span><span class="Apple-tab-span"></span><span class="Apple-tab-span"></span></span> <LinearLayout</span></div>
<div class="p3">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span> </span> android:layout_width<span class="s4">=</span><span class="s5">"fill_parent"</span></span></div>
<div class="p3">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span> </span> android:layout_height<span class="s4">=</span><span class="s5">"wrap_content"</span></span></div>
<div class="p3">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span> </span> android:orientation<span class="s4">=</span><span class="s5">"vertical"</span><span class="s2"> </span><span class="s1">></span></span></div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s1"><RatingBar</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s3">android:id</span><span class="s4">=</span><span class="s5">"@+id/rtbDvcMgmt"</span></span></div>
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"><span class="Apple-tab-span"> </span> </span> <span class="s2"> </span><span class="s3">style</span><span class="s4">=</span>"@style/CustomRatingBar"</span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s3">android:layout_width</span><span class="s4">=</span><span class="s5">"wrap_content"</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s3">android:layout_height</span><span class="s4">=</span><span class="s5">"wrap_content"</span></span></div>
<div class="p3">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"><span class="Apple-tab-span"> </span> </span> android:layout_marginRight<span class="s4">=</span><span class="s5">"25dp"</span></span></div>
<div class="p3">
<span style="font-family: Courier New, Courier, monospace;"><span class="s2"><span class="Apple-tab-span"> </span> </span> android:layout_marginTop<span class="s4">=</span><span class="s5">"5dp"</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s3">android:max</span><span class="s4">=</span><span class="s5">"5"</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s3">android:maxHeight</span><span class="s4">=</span><span class="s5">"50dp"</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s3">android:minHeight</span><span class="s4">=</span><span class="s5">"28dp"</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s3">android:numStars</span><span class="s4">=</span><span class="s5">"5"</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s3">android:rating</span><span class="s4">=</span><span class="s5">"0.0"</span></span></div>
<div class="p4">
<span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"> </span> <span class="s3">android:stepSize</span><span class="s4">=</span><span class="s5">"1.0"</span> <span class="s1">/></span></span></div>
<div class="p7">
<span style="font-family: Courier New, Courier, monospace;"><span class="s4"><span class="Apple-tab-span"></span><span class="Apple-tab-span"></span><span class="Apple-tab-span"></span><span class="Apple-tab-span"></span></span> </LinearLayout></span></div>
<div class="p7">
<span style="font-family: Courier New, Courier, monospace;"></TableRow></span></div>
</div>
<div class="p7">
</div>
Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com61tag:blogger.com,1999:blog-5332582024103750170.post-58212848996358917532013-01-07T23:02:00.001-05:002013-01-07T23:02:50.246-05:00My WeatherPi - Weather reports from a Raspberry PI<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3NrZBNeFxdiabgJrPs3ZLfUSZSryHEU-PKyRi7TqDCcWafeuoGvQsbw0BO0_Q3QUI18ZImMiD71aMmNplcHM7IN4jS_5gCPDEPPU1Rba0BhDkMEVuToYci_TXZHUk71wGAoA8nNtuX42-/s1600/%255BUNSET%255D" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3NrZBNeFxdiabgJrPs3ZLfUSZSryHEU-PKyRi7TqDCcWafeuoGvQsbw0BO0_Q3QUI18ZImMiD71aMmNplcHM7IN4jS_5gCPDEPPU1Rba0BhDkMEVuToYci_TXZHUk71wGAoA8nNtuX42-/s320/%255BUNSET%255D" width="320" /></a></div>
<b id="internal-source-marker_0.0020710425451397896" style="font-weight: normal;"><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">Ever find yourself pining away at </span><a href="http://www.wunderground.com/"><span style="color: #1155cc; font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">Wuderground</span></a><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;"> and wishing you had one of those really cool weather stations proudly poking its uber-scientific like visage over your neighbors fence? Yes me too, not only that but it runs in the family because my dad also had that goal. Being a sucker for electronics, weather stations and a great gift idea I bought my dad a wh1080 Weather Station for Christmas. We got it all set up and found the software to be a bit.. dated. It doesn’t upload to the internet and though there are programs out there to take care of that for you, Dad really didn’t want want his weather station shackled to a PC via USB. </span><br /><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">Now that I have amassed a small army of R-Pis to do my bidding (4 and counting) I figured it was time to start gifting them with useful software to family and friends. So I set up a WeatherPi, the nickname I have assigned this project and the WeatherPi is rocking! The best part is that setting up a WeatherPi is a great way to get to know a lot of the key features of the Linux/Pi OS and prepare your *nix skills for future projects.</span><br /><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">First up, lets look at the stats:</span></b><br />
<div dir="ltr" style="margin-bottom: 0pt; margin-top: 0pt; text-indent: 36pt;">
<b id="internal-source-marker_0.0020710425451397896" style="font-weight: normal;"><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">Raspberry Pi - Older 256Ram version.</span></b></div>
<div dir="ltr" style="margin-bottom: 0pt; margin-top: 0pt; text-indent: 36pt;">
<b id="internal-source-marker_0.0020710425451397896" style="font-weight: normal;"><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">SD - 8mg</span></b></div>
<div dir="ltr" style="margin-bottom: 0pt; margin-top: 0pt; text-indent: 36pt;">
<b id="internal-source-marker_0.0020710425451397896" style="font-weight: normal;"><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">Edimax EW-7811 (Usb WiFi)</span></b></div>
<div dir="ltr" style="margin-bottom: 0pt; margin-top: 0pt; text-indent: 36pt;">
<b id="internal-source-marker_0.0020710425451397896" style="font-weight: normal;"><b id="internal-source-marker_0.0020710425451397896" style="font-weight: normal;"><a href="http://jim-easterbrook.github.com/pywws/doc/en/html/index.html"><span style="color: #1155cc; font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">Pywws</span></a><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;"> with Python</span></b></b></div>
<div dir="ltr" style="margin-bottom: 0pt; margin-top: 0pt; text-indent: 36pt;">
<b id="internal-source-marker_0.0020710425451397896" style="font-weight: normal;"><b id="internal-source-marker_0.0020710425451397896" style="font-weight: normal;"><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">TP1080WC ProWeatherStation aka: wh1080 (kinda a must have)</span></b></b></div>
<b id="internal-source-marker_0.0020710425451397896"><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;">Now I must admit that this is more of an OS based project then electronics build but, if you're just getting started and you really want to master some of the Linux skills you really can’t go wrong with this project. Here’s some of the fun new skills you could pick up while getting your weather station data to Wunderground.com </span><br /><ol style="font-weight: normal; margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; font-size: 15px; list-style-type: decimal; vertical-align: baseline;"><span style="vertical-align: baseline; white-space: pre-wrap;">Basic RPi setup - I used the Debian wheezy install right of the RaspberryPi site. </span></li>
<li dir="ltr" style="font-family: Arial; font-size: 15px; list-style-type: decimal; vertical-align: baseline;"><span style="vertical-align: baseline; white-space: pre-wrap;">Usb-wif and ssh because I like my big monitor and I didn’t want the RPi hooked up to the TV.</span></li>
<li dir="ltr" style="font-family: Arial; font-size: 15px; list-style-type: decimal; vertical-align: baseline;"><span style="vertical-align: baseline; white-space: pre-wrap;">Setting a static ip - I want to know where this guy is on my network so I can connect easily.</span></li>
<li dir="ltr" style="font-family: Arial; font-size: 15px; list-style-type: decimal; vertical-align: baseline;"><span style="vertical-align: baseline; white-space: pre-wrap;">Pywws - a great open source tool for uploading to Wunderground and 4 or 5 other sites and more.</span></li>
<li dir="ltr" style="font-family: Arial; font-size: 15px; list-style-type: decimal; vertical-align: baseline;"><span style="vertical-align: baseline; white-space: pre-wrap;">Chrontab configuration and shell scripts - used to set up the Pywws to run the LiveLog every 5 minutes and upload WeatherStation data.</span></li>
</ol>
<span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">References<span style="font-weight: normal;"><br /></span></span></b><div>
<b><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;">I would like to go into the details on how each part should be set up and configured but, I think it’s only right to give credit where it is due so I’ll provide links to all the great blogs where I gathered my info.</span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;">Overall Project info as well as specific Pywws installation advice:</span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><a href="http://blog.retep.org/2012/07/30/installing-a-usb-weather-station-on-a-raspberry-pi-part-1/" style="font-weight: normal;"><span style="color: #1155cc; font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">http://blog.retep.org/2012/07/30/installing-a-usb-weather-station-on-a-raspberry-pi-part-1/</span></a><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;">Edimax usb wifi setup: (With notes on a static ip)</span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><a href="http://etchingpathways.blogspot.com/2012/12/raspberry-pi-edimax-ew-7811un-wifi.html" style="font-weight: normal;"><span style="color: #1155cc; font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">http://etchingpathways.blogspot.com/2012/12/raspberry-pi-edimax-ew-7811un-wifi.html</span></a><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;">Chrontab (second half covers chrontab)</span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><a href="http://blog.davidsingleton.org/raspberry-pi-webcam-a-gentle-intro-to-crontab/" style="font-weight: normal;"><span style="color: #1155cc; font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">http://blog.davidsingleton.org/raspberry-pi-webcam-a-gentle-intro-to-crontab/</span></a><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;">Weatherstation: (It’s on amazon as well)</span></b></div>
<div>
<a href="http://www.rainmanweather.com/site/catalog/Weather-Stations/Tycon-Power"><span style="color: #1155cc; font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">http://www.rainmanweather.com/site/catalog/Weather-Stations/Tycon-Power</span></a><span style="font-family: Arial;"><span style="font-size: 15px; white-space: pre-wrap;"><br /></span></span><div dir="ltr" style="font-weight: normal; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgelYsLEmj6nrnyyzeE6ajk4hNLuwDSaplE18_Xlaw4Iife_xDRwfILN1nGkj5Z_UVZySRnrVLvDyKpGNVPPX_eASM1L7_eE_S_G4h3uSk0aYPCX-sIddE9A9mRXIGo5KEY-dgBdUhfqTHt/s1600/tyconpowerWS.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgelYsLEmj6nrnyyzeE6ajk4hNLuwDSaplE18_Xlaw4Iife_xDRwfILN1nGkj5Z_UVZySRnrVLvDyKpGNVPPX_eASM1L7_eE_S_G4h3uSk0aYPCX-sIddE9A9mRXIGo5KEY-dgBdUhfqTHt/s320/tyconpowerWS.jpg" width="284" /></a><br /><span style="font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;"></span></div>
<span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: bold; vertical-align: baseline; white-space: pre-wrap;">Wrap-up and Next Steps</span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;">I originally picked up this project to help out my Dad with his new Weather Station but after working on the set up for a couple hours at my parents house using my Tablet to ssh into the PI and configure I decided to take the PI back home to finish things off. Of course then I didn’t have a Weather Station to verify all the config and installs... they live half an hour away so only one choice, buy my own weather station! Really I didn’t plan it this way, honest I didn’t! I wrapped up the Pywws install and all the chrontab stuff at home and then made a copy of the SD card image with all the good configurations already done and poof! 2 WeatherPis ready to go.</span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;">Now as any IT pro can tell you, the moment you build, config, install or even look seriously at a device, you own support! With this in mind I have already set up a PI script to call my own REST Web Services to report to me when there are problems. This has led me on to start developing one of my next projects, HomePi, which will allow all my little Pi ‘s t check in with the HomePi and tell me how they are doing and what sort of errors they encounter while doing their thing. It’s pretty basic right now but I am hoping to allow for code updates and other support functions in the future. </span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><br /><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;">I have plans to integrate </span><a href="https://ifttt.com/" style="font-weight: normal;"><span style="color: #1155cc; font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">IFTTT</span></a><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"> to work to provide Freeze and drought warnings for my Garden and most likely I’ll be logging my weather data over at </span><a href="https://cosm.com/" style="font-weight: normal;"><span style="color: #1155cc; font-family: Arial; font-size: 15px; vertical-align: baseline; white-space: pre-wrap;">COSM</span></a><span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"> (formally known as Pachube) just because I can.</span></div>
Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com3tag:blogger.com,1999:blog-5332582024103750170.post-75136637972137700042012-12-04T13:00:00.000-05:002013-01-07T22:48:52.126-05:00Spring, Autowire and EasyMock I've been stalling on writing a number of tests I needed because I was caught up on how easily inject a Mock into a Service that I wanted to test. The problem was that I didn't want to go to the trouble of cooking up a separate spring context just for tests not to mention I couldn't rely on data in the DB for the DAO to utilize after all the point of EasyMock is... EASY! Well that avoiding writing that test that needed a mock finally bit me when some fast refactoring caused something fail because of the lacking test. That's what ya get, right? <br />
<br />
After a good deal of hunting I come across a simple solution that I am surprised was so hard to find...<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">ReflectionTestUtils.setField(serviceYourTesting, "autowireBeanName", yourMock);</span><br />
<br />
That's it! No, really. OK OK, that's not all you have to do. You are messing with the Service Beans properties so if you have other tests elsewhere that expect that mock object to not be a mock object you'll have to undo what you did. This isn't such a hassle though. simply call the following and stash that object to restore after you done with the mock.<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Object saveBean = ReflectionTestUtils.getField(serviceYourTesting, "autowireBeanName");</span><br />
<br />
I would suggest wrapping your test in a try/finally so you can make sure that the original bean value is set back before the tests concludes. If you don't do this other tests that expect the original bean may fail.<br />
<br />
Happy Spring Mocking!<br />
<br />Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0tag:blogger.com,1999:blog-5332582024103750170.post-29661246379771493162012-10-10T18:30:00.000-04:002013-01-07T22:43:33.848-05:00java.lang.reflect.MalformedParameterizedTypeException or (WTH is wrong with my deployment now?)So the fun part of the <b>MalformedParameterizedTypeException </b>error is that it seems to be caused by many different things at least that is the impression that I get from the Google searches I conducted and the various suggestions for a fix offered on StackOverflow. I think it really comes down to a simple incompatible jar file hanging out on your web server.<br />
<br />
I'n my case I am using WebLogic and the local version of WL that I have running right now is 10.3.6, the version on my Dev server is 10.3.5; you wouldn't expects that a version that close together would be such trouble but I cost me a day to figure out what's up. First off I still don't know the name and version of whatever jar is causing the trouble in Dev. I don't have the access to sort it out and now that I know the cause... I just wont do it, problem avoided though not solved. Good enough under a tight deadline.<br />
<br />
So on to the point! Lets say your using Spring as I am and you have a DAO that doesn't require a dataSource because it's just not that kind of DAO. So you don't have to inject anything into the object. Well when you define this DAO and you deploy only to get our friend <b>MalformedParameterizedTypeException </b>popping up, the cause is defining the bean in an abbreviated xml node such as the following:<br />
<br />
<br />
<code><bean class="com.myco.webservices.dao.impl.MyDaoImpl" id="myDao"/></code><br />
<br />
The fix is so simple: (Note the ending tag)<br />
<br />
<code><bean class="com.myco.webservices.dao.impl.MyDaoImpl" id="myDao"></bean></code><br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><br />
<br />
Of course finding that issue in a dozen files with multi-line changes, imports of new packages and implementation of new functionality is um... not so easy.<br />
<br />Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0tag:blogger.com,1999:blog-5332582024103750170.post-5386485772985991222012-06-20T17:13:00.002-04:002012-06-21T15:40:13.243-04:00The In and Outs of MongoDB and Jackson<br />
<div class="separator" style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg78U7SIWMlEmzpfmIdfOpKs-H1HbULJE2yp7iJuxqL9v3YHh1xHtAyhvYvIyEp0ITuE5zlQ2BzrTPxyLJbgRc5RSi5ivdxYL19hxKFVNEMpR0Ege9cMo5ZL94lIm4ruVzVVnZZeX5CJPFB/s1600/mongoIcon.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg78U7SIWMlEmzpfmIdfOpKs-H1HbULJE2yp7iJuxqL9v3YHh1xHtAyhvYvIyEp0ITuE5zlQ2BzrTPxyLJbgRc5RSi5ivdxYL19hxKFVNEMpR0Ege9cMo5ZL94lIm4ruVzVVnZZeX5CJPFB/s1600/mongoIcon.png" /></a></div>
MongoDB has been a great NoSQL solution to work with so far but, there are a few gotchas that I found needed some exploring to get sorted out properly for a Mongo noob.<br />
<br />
I have been working with RESTful services for the past couple years but to date, my NoSQL solution has primarily been Membase. Membase is good at what it does but, it is a bit sparse on features compared to the RDBMs that I am accustomed to working with. No, don't worry I am not about to jump into a debate of NoSQL vs RDBMs, I use my soldering iron to solder and my multi-meter to measure volts, each tool for its job! On the matter of Membase vs MongoDB though I have to say that I have quicky developed a strong preference for Mongo. This new appreciation comes from the field indexing and the ability to use regex to search for specific data. Perhaps this is just me finding some similarities between RDBM and Mongo? Sure that and living with out those features in the past has simply resulted in writing code for things that are usually standard issue.<br />
<br />
In my current project I am using MongoDB with Java JAX-RS and Jackson. So far things have fit together quite nicely though I did come across a few issues that I had a hard time tracking down detailed information about. I had to just puzzle out these issues and thought someone might find the info useful, not to mention I can look them up when I inevitably forget them in 6 months once all that information is replaced with whatever comes next.<br />
<br />
One of those things I love about a MySQL db driver is that it thoughtfully returns the ID for a newly created record so you can easily access it for further use or abuse. MongoDB also does this but, it wasn't at all obvious how the Java driver was returning this value. At first I was doing a query back to the collection to grab the new _id but, this method left me just feeling less then confident that things would always turn out right. What I didn't realize was the Java driver was thoughtfully returning the value by inserting it in the BasicDBObject used to create the insert. I felt a little silly not thinking to look there first but.. well it just wasn't what I was expecting. Here's the code to illustrate.<br />
<br />
<br />
<pre class="prettyprint lang-java linenums">BasicDBObject dbObject = MongoModelFactory.buildDBObject(model);
DBCollection dbColl = getCollection(collectionName.getValue());
WriteResult result = dbColl.insert(dbObject);
//The _id values is populated in the dbObject after the insert.
dbObject.get("_id");
</pre>
<br />
Related to getting the id comes a Jackson issue. Because the _id is not stored as a String, but as an ObjectId which makes deserializing the value with the Jackson ObjectMapper a bit on the <span style="background-color: white;">complicated side.</span><span style="background-color: white;"> The trick here is to create a MongoID object which contains a JsonCreator that knows how to construct the _id.$oid value from the JSON. This was by far the simplest way to use Jackson to get this information out and the object is reusable across all of my DataObjects. Here's the code:</span><br />
<br />
<pre class="prettyprint lang-java linenums">public class MongoID {
private String $oid;
public MongoID(){
}
public String get$oid() {
return $oid;
}
public void set$oid(String $oid) {
this.$oid = $oid;
}
@JsonCreator
public static String fromJSON(String val)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
MongoID a = mapper.readValue(val,MongoID.class);
return a.get$oid();
}
}</pre>
<br />
The above class is utilized in my BaseModel abstract object to provide a hint to the Jackson ObjectMapper as to what field this is basically by providing a fromJSON method for Mongo's <span style="background-color: white;">ObjectId</span><span style="background-color: white;"> . </span><br />
<br />
The last challenge I ran into was also a Jackson related issue, specifically in serializing an object to JSON and back again. Jackson doesn't like really complicated JSON lists and due to class erasure it has a hard time figuring out what sort of objects are contained in a list. Such a case is simular to the following JSON:<br />
<br />
<pre class="prettyprint">{
"_id": "4fc151c9ebb11be7d1ae4905",
"mapPreferences": {
"bgColor": "#fff",
"gridColor": "#000"
},
"maps": [
{
"cols": 30,
"title": "TestMap",
"rows": 30
},
{
"cols": 32,
"title": "TestMap2",
"rows": 32
}
]
}
</pre>
<br />
Jackson doesn't really have issues with a list of strings but try a list of objects or a multi-dimensional array and Jackson is stumped as to what to do because of alack of information. The JsonCreator again provides a solution to this issue! In the above example we have a field called "maps" which contains an array of MapData Objects, with out providing a JsonCreator, Jackson won't know what object is contained in the list and can't instantiate a list with objects of that type. To solve this List of unknown objects issue I have written the folloing code in the MapData Object :<br />
<br />
<pre class="prettyprint lang-java linenums">@JsonCreator
public AdventureData(@JsonProperty("adventureId") String advId,
@JsonProperty("mapPreferences") MapPreferences mapPrefs,
@JsonProperty("maps") Object maps)
throws JsonParseException, JsonMappingException,
IOException
{
this.adventureId = advId;
this.mapPreferences = mapPrefs;
ObjectMapper mapper = new ObjectMapper();
String mapsString = mapper.writeValueAsString(maps);
this.maps = mapper.readValue(mapsString,
TypeFactory.collectionType(List.class, MapData.class));
}
</pre>
<br />
In the constructor above the parameters define the objects but in the case of the "maps" field it is left to be an Object because it will be deseralized separately since casting to a List is obfuscated by Type Erasure. To construct the List of MapData objects I then used the ObjectMapper to convert the list back to a JSON String then deserialize it! To make it clear to the ObjectMapper what sort of object is in the List I use the TypeFactory to indicate that we have a Collection of the List type here and it contains MapData. That's about all there is too that. You will need to give the MapData object it's own JsonCreator similar to the one above but that's all you have to do. If only it had been that easy to find a sample on how this is done! I will say that if you know of another way to make this happen please let me know because the Object to string conversion feels a little bit like a Hack though it works perfectly.<br />
<br />
I hope this helps you out when your working with Mongo and Jackson for the first time! (After reviewing this post I have come to realize that I will need to describe my generics based DAO that I am using for MongoDB to better explain what I'm doing with the _id value. Look for it in the future!)<br />
<br />
<br />Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0tag:blogger.com,1999:blog-5332582024103750170.post-22838361985778394842011-06-06T17:08:00.002-04:002011-06-13T20:17:42.229-04:00A solution to speed up an old DroidIs your Droid phone running so slow that you can barely answer a phone call before it rolls to voice mail? Yea that was my phone last week! I couldn't accomplish anything with my phone without long delays and that included just answering the phone! I have a venerable but, much loved Droid 1 and with 4 months remaining on the contract there was just no way I could "tough it out" at the current response time. Discussing the issue with a few Verizon reps got me the well worn mantra of the old Windows OS days, Reset (Just like Reinstall when you had Windows speed problems right?).<br />
<br />
Fortunately, I found a better solution. I didn't want to invest the time into re-configuring and downloading everything on my phone, it's just the way I like it and I have better things to do such as working on my Game Programming or gaming with my son! I had freed up more memory then the average amount of free memory found on a Droid so I knew that the OS had enough space to do it's thing which lead me to suspect something was getting in the way of Androids ability to do things quickly. The first thing that came to mind? Cache. Nothing like a nice fat Cache full of useless junk to keep an old processor spinning its wheels right? Sure enough!<br />
<br />
So my suggestion, before you reset your Android, clear the system Cache, it just might save you a couple hours! Here's the surprisingly easy Steps (Getting into the System Menu is specific to the Droid 1, but the steps should be the same on other Droids)<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEix_unCkkvFTZ7tvaf8cWBPDgBO-EQ17LX327XFXtXYrcvdMpDb95wr0l2x2gGrokJGVgTMT1dxKmHIAOJB_J5WmKq_9R_bEAIgniWXPjUpHb_gG2tyATQwiSGmEwHNqi5jYytnaFF_MKbz/s1600/myMenu.JPG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEix_unCkkvFTZ7tvaf8cWBPDgBO-EQ17LX327XFXtXYrcvdMpDb95wr0l2x2gGrokJGVgTMT1dxKmHIAOJB_J5WmKq_9R_bEAIgniWXPjUpHb_gG2tyATQwiSGmEwHNqi5jYytnaFF_MKbz/s1600/myMenu.JPG" /></a>1. Turn the Droid off, take the battery out then place it back in.<br />
<br />
2. Open the keyboard and press the <b>X</b> key while pressing and holding Power. The device will power on. Keep holding the buttons down until a yellow triangle appears on the screen.<br />
<br />
3. Press and hold the <b>Volume Up</b> and then also press <b>Camera button</b> button to get to the "Android System Recovery" Menu.<br />
<br />
4. Use the direction pad to the right of the keyboard to select the "<b>wipe cache partition</b>" (This is it right here!)<br />
<br />
5. Once complete, use the direction pad to select the first option "reboot"<br />
<br />
<br />
Believe it or not this did the trick! My phone has been running much faster, it's still a near two year old phone so its not as peppy as it once was but hey we all get a little bit slower with age...Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0tag:blogger.com,1999:blog-5332582024103750170.post-14280371752908161412011-04-26T16:47:00.000-04:002011-04-26T16:47:27.352-04:00Moving to Siena from Play Framework JPAI Recently built a REST service using the Play Framework and once complete I started looking for a place to deploy the app. Not having a hosted server that runs Java meant I needed a cloud solution and the first to come to mind was GAE. I have used GAE before but this was the first time with Play. (Play and GAE go great together btw.)<br />
<br />
Having done little deployment planning from the beginning, it wasnt until a blew through the coding and did a successful deploy that I realized that JPA just doesn't seem to work well with Play's JPA (nothing against Play, GAE just does JPA differently). After a bit of reading that I had skipped past the first time it became evident that I needed to use the Play <a href="http://www.sienaproject.com/index.html">Siena</a> Module with my GAE app. This turned out to be easier then I though but, I had to hunt around a little to piece together the details so I wanted to share the migration here. This should also be helpful if you are planning to use Siena from the start and already know Play's JPA. <br />
<br />
Moving to Siena from the Play JPA was surprisingly simple, I spent more time thinking over weather I wanted to mess with it and looking up the details then actually doing it!<br />
<br />
1. <i><b>Install Siena into Play</b>.</i> (I'm assuming you have Play set up and GAE already installed.)<br />
<code>play install siena-1.5</code><br />
<br />
2. <b>Include in App.conf</b>. Add Siena right under the GAE module like so.<br />
<code><br />
# ---- MODULES ----<br />
module.gae=${play.path}/modules/gae-1.4<br />
module.siena=${play.path}/modules/siena-1.5<br />
</code><br />
<br />
3.<b> Change your Models from play.db.jpa.Model to siena.Model</b>. Your just changing an import statement here.<br />
<code><br />
//import play.db.jpa.Model; <br />
import siena.Model; <br />
</code><br />
<br />
4. <b>Add an ID field</b>. Play takes care of this for you in the <i>Model</i> but you'll have to add it in for Siena, or make a new Object to inherit from.<br />
<code><br />
@Id<br />
public Long id;<br />
</code><br />
<br />
5. <b>Update method calls</b>. A few methods have slightly different names but they are pretty obvious and should slow you down.<br />
<ol><li>Model.save() calls become Model.insert()</li>
<li>You'll need to change up your searching, I usually just code methods in the <i>Model </i>to wrap the search queries which means I only have to change the search code in one spot, the <i>Model</i>. Here's a sample of a new search:</li>
<code>Model.all(MonitoredFeed.class).filter("suspended", false).fetch();</code>
<li>Change findAll(), all() calls to Model.All(Your.class) or simply provide a helper method like below.(This could again go in some type of <i>BaseModel </i>reducing your code changes.)</li>
<code> public static Query<monitoredfeed> all() { return Model.all(MonitoredFeed.class); } </monitoredfeed></code> </ol>6. <b>Update your Test Fixtures!</b> Of course you have to update the Tests! If your using Fixtures you'll just have to change your imports from <i>Fixture </i>to <i>SienaFixture</i>.<br />
<br />
That's all there its to it! Now you can deploy your app on GAE!Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com1tag:blogger.com,1999:blog-5332582024103750170.post-64427536564674347062011-04-16T09:54:00.005-04:002011-04-16T09:54:00.296-04:00Serve Up Static Content Even When Mapping /* to a FrontController Servlet.<i>I am blogging this to share the info as much as to remind myself in the future how simple setting this up really is. It's the easy solutions that can be the hardest to remember..</i><br />
<br />
Say you have a RESTful services framework running in a web app and all of your requests get routed to CXF or some other Framework for supporting such implementations. Simply put, there isn't a UI for the app and the Controller Servlet does't support a UI. Now say that you really want to be able to serve up say.. a JS file and some css to style generated API documentation for your services. No problem since this is Tomcat except you have a <b>/*</b> url-pattern routing all traffic to a REST Servlet. <br />
<br />
Here's the solution: Most Java servers have a default servlet of some sort which will simply handle HTTP requests. If you set up a url-pattern to /static or some other path you can direct that request to your static files. One note, you'll be requesting /static/js/jquery-min.js but, the fiel will reside at /js/jquery-min.js this is because the /static is really acting as a indicator to rout to the default servlet. It's almost like a query string in the path.. no it makes since, really.<br />
<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code><servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/static/*</url-pattern>
</servlet-mapping>
</code></pre>Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0tag:blogger.com,1999:blog-5332582024103750170.post-24926082435915428792011-04-14T19:49:00.001-04:002011-04-19T10:58:18.003-04:00Scripting Groovy to process csv files.I have a lot of experience with um, "manhandling" flat file data into usable formats. Usually this "data" comes to me in the form of excel spreadsheets, crafted with presentation in mind. They look great but, have little flexibility when it comes to importing the data into relational databases. There are hundreds of solutions for doing this sort of manipulation in bulk and if that's your lot in life by all means beg the IT department to buy you a good data transformation tool!<br />
<br />
For some reason my situations are frequently one offs of user data that is begging to get into a database but, as of yet is still running in the wild. My solution has come down to quick Groovy scripts and splicing spreed sheets into multiple CSV files Groovy to chew through. This can be done with Python and many other script languages but, I'm fully fluent in Java so Groovy is my fastest path to a database at this point in time. This is a great place to learn a new scripting language if you have the time but, I usually want this over in a flash. <i>On that note, if you would like to just grab the code and run jump to the bottom to grab My CsvUtils code which takes care of this whole process in a more formal utility object.</i><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7chAmmiLMQXC4Ez6CLd3k6Hn__9JHnBXjd3JI5zyru_L6RtUxMV3WskTK8boZ9Ov-MaAF9SULk47F9ozoFv6Yi0MBkl4FiGdiIW-YngDTybi9IYRsVQkfHwvkQ786f162BRA6_gEaMT8n/s1600/groovy-logo.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7chAmmiLMQXC4Ez6CLd3k6Hn__9JHnBXjd3JI5zyru_L6RtUxMV3WskTK8boZ9Ov-MaAF9SULk47F9ozoFv6Yi0MBkl4FiGdiIW-YngDTybi9IYRsVQkfHwvkQ786f162BRA6_gEaMT8n/s1600/groovy-logo.png" /></a></div><br />
Reading a file and working with it is pretty darn simple in Groovy, the following code will open up a CSV and move all the data into nicely structured arrays. (This provides the same results as calling CSVUtils.csvToList())<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUX9c7TcFsmu7snk7Gh2KVW6he1m-jQE1piH412772I6RwyUfX49N8rpUvmF2HC1Fyt-uxiet7NWf9o0c-Sr2iqZzWyH4hvXnahtwH1-D49Kia5J7R35PvN8a17hUz_La-Dk3kVDfiB4r9/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 22px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> //load room types from file
def f= new File('roomtypemap-min.csv');
//set up a List of 'Rows' which will contain a list of 'columns' or you can think of it as array[][]
roomTypes = [];
def lines = 0;
f.splitEachLine(',') {
def row = [];
row = it;
roomTypes << row;
lines++
}
println "Processed $lines Lines"
</code></pre><br />
The above chewed through a CSV file with name,value pairs of data and placed them in a multi-dimensional array types as a Java list. If your not up on Java collections, it's a whole lot like an array but, better. <br />
<br />
Now lets say we did the above code with a few files then process all that data into a new CSV file ready for your code. The code below shows the building of the final results. You'll notice that along with the <i>roomTypes</i> list imported from a CSV file there are now a few others <i>resortList</i> and <i>amenities</i>. All these were pulled from random table dumps and such and I turned them in to usable data.<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUX9c7TcFsmu7snk7Gh2KVW6he1m-jQE1piH412772I6RwyUfX49N8rpUvmF2HC1Fyt-uxiet7NWf9o0c-Sr2iqZzWyH4hvXnahtwH1-D49Kia5J7R35PvN8a17hUz_La-Dk3kVDfiB4r9/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 22px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">
def aId = 0;
roomTypes.each{
def indx = roomTypes.indexOf(it);
def resortList = roomResortMap[indx];
def roomType = it[0];
resortList.each() {
def facIdx = it.toInteger();
def facilityId = resorts[it.toInteger()];
amenities.each() {
def ai = amenities.indexOf(it);
def avail = values[ai];
if(avail[facIdx] == true) {
def description = it;
println "\"$facilityId\",$roomType,$aId,\"$description\"";
aId++;
}
}
}
</code></pre><br />
There are a couple things to point out if your new to Groovy. While there is a whole lot of Java in Groovy it also looks a lot like JavaScript making Groovy pretty easy for JavaScript developers to pick up despite deep Java knowledge.<br />
<br />
The first thing to note is the iteration I am performing with the .each callback. Personally, I think it looks a good bit like a JavaScript closure. Your simply passing in code to the .each(){} iterator to execute on each item in the List. OH the () is optional when creating this closure so add if you wish.<br />
<br />
Second, in the version of Groovy I am using, 1.7.10, you have to do a little bit of work to get the current index position of the for loop. To do this simply call yourListName.indexOf(it). It looks like the upcoming 1.8 is going to have an additional closure called eachWithIndex() which will provide you <i>it</i> and <i>i </i>variables where the <i>i</i> will be the current index. I'm looking forward to that feature!<br />
<br />
Finally I just want to point out one of my favorite things for keeping life simple, string replacement. Notice on the next to list line before the braces cascade I'm calling <i>println</i> but, the cool part is the replacements in the string using $fieldName. When your trying to print out complicated strings nothing is worse for readability and bugs then concatenating or calling tons of append() methods. It just leads to errors. You can do simple string replacement with variables using the $fieldName inside your string or for more complicated inserts ${} will work for things like ${Object.field}.<br />
<br />
Hope this will help you to simplify the tedious task of data mining text files!<br />
<br />
<b>My CSVUtils Object</b>:<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUX9c7TcFsmu7snk7Gh2KVW6he1m-jQE1piH412772I6RwyUfX49N8rpUvmF2HC1Fyt-uxiet7NWf9o0c-Sr2iqZzWyH4hvXnahtwH1-D49Kia5J7R35PvN8a17hUz_La-Dk3kVDfiB4r9/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 22px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">
/**
* CSVUtils is released into the public domain, do what ya want with it.
* By Lee Clarke
*/
public class CSVUtils {
/**
* Removed quotes from around imported csv values if present.
*/
public static String stripQuotes(String strIn) {
def valOut = strIn;
if(valOut==null)
valOut == "";
if(valOut.startsWith("\""))
valOut = valOut.substring(1);
if(valOut.endsWith("\""))
valOut = valOut.substring(0,valOut.length()-1);
return valOut;
}
/**
* Load the csv into a Map using indexed position of values for key and value.
* @param keyPos - col index of the key value in a row
* @param valPos - pull value data from index/col position in row
* @param csvFilePath - full path to file
*/
public static Map csvToMap(int keyPos, int valPos, String csvFilePath) {
def fp= new File(csvFilePath);
def rtnMap = [:];
def palines = 0;
fp.splitEachLine(',') {
if(palines > 0)//skip col header line
{
def row = [];
row = it;
def key = CSVUtils.stripQuotes( row[keyPos]);
def val = CSVUtils.stripQuotes( (valPos < 0)?row.last():row[valPos]);
rtnMap.putAt(key, val);
}
palines++
}
return rtnMap;
}
/**
* loads values in csv file into multi-dimentional like List of rows and cols.
* @param csvFilePath - full file path.
* @param skipFirstRow - skip first row if it contains column names.
*/
public static List csvToList( String csvFilePath, boolean skipFirstRow) {
def fr= new File(csvFilePath);
def rtnList = [];
def rlines = 0;
fr.splitEachLine(',') {
if(!(skipFirstRow && rlines == 0)) {
println "row= $it"
def row = [];
row = it;
rtnList << row;
}
rlines++
}
return rtnList;
}
/**
* loads values in csv file into multi-dimentional like List of rows and cols.
* @param csvFilePath - full file path.
*/
public static List csvToList( String csvFilePath) {
return csvToList(csvFilePath,true);
}
}
</code></pre>Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com5tag:blogger.com,1999:blog-5332582024103750170.post-9872858508762662532011-04-08T11:12:00.000-04:002011-04-08T11:12:01.648-04:00New hRecipeHelper for Web Browsers released!Do you like to blog recipes but don't use Chrome? No problem! Now you can use my new web version of <a href="http://bit.ly/hPcVDD">hRecipeHelper </a> in any moder browser. I know it works with FireFox 3.6 and up as well as IE8, I can only assume it works with IE9 if IE8 works. I haven't tested on IE9 and Safari because I don't have them and can't even run IE9. If you trying it out and find bugs please post a comment here and I'll have it fixed asap. <br />
<br />
Just like the Chrome Extension its free, Enjoy!Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com3tag:blogger.com,1999:blog-5332582024103750170.post-54346139902827707832011-03-25T21:56:00.001-04:002011-03-28T10:02:01.113-04:00Validating your microformat pages<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 16px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"><span class="Apple-style-span" style="font-family: 'Trebuchet MS', Trebuchet, Verdana, sans-serif;"><span class="Apple-style-span" style="line-height: 1.4em;">I posted some information about validating microformats over on the hRecipeHelper wiki but, I felt that it was worth a quick blog posting just to share with others that aren't likely to find a wiki page </span><span class="Apple-style-span" style="line-height: 18px;">buried</span><span class="Apple-style-span" style="line-height: 1.4em;"> on Guthub!</span></span></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 16px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"><span class="Apple-style-span" style="font-family: 'Trebuchet MS', Trebuchet, Verdana, sans-serif;"><span class="Apple-style-span" style="line-height: 18px;">I built a Chrome extension that will format recipes for blog postings using the hRecipe microformat and once done I wanted to test my results to make sure they were hRecipe friendly as well as to ensure that Google would find them tasty and digestible. To my surprise I did find a few very useful resources for the task.</span></span></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 16px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"><span class="Apple-style-span" style="font-family: 'Trebuchet MS', Trebuchet, Verdana, sans-serif;"><span class="Apple-style-span" style="line-height: 18px;">The first handy resource</span></span><span class="Apple-style-span" style="font-family: 'Trebuchet MS', Trebuchet, Verdana, sans-serif; line-height: 18px;"> is of course the microformat <a href="http://microformats.org/wiki/hrecipe" style="color: #4183c4; line-height: 1.4em; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; outline-color: initial; outline-style: none; outline-width: initial; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-decoration: none;">hRecipe</a> where the specification is being defined. The format is a work in progress and currently I am supporting version 0.22 though I plan to continue to update the hRecipeHelper extension as the specification matures.</span></div><div style="font-family: 'Trebuchet MS', Trebuchet, Verdana, sans-serif; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 16px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"><span class="Apple-style-span" style="line-height: 1.4em;">The second resource is a nice validation tool that Google has made available that will review your web page and try to parse out the hRecipe data as well as other microformats. It's simple to use, just paste any link into the form field on the page right here </span><a href="http://www.google.com/webmasters/tools/richsnippets" style="color: #4183c4; line-height: 1.4em; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; outline-color: initial; outline-style: none; outline-width: initial; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-decoration: none;">Rich Snippets Testing Tool</a><span class="Apple-style-span" style="line-height: 1.4em;"> and you'll see how it holds up. </span></div><div style="font-family: 'Trebuchet MS', Trebuchet, Verdana, sans-serif; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 16px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"><span class="Apple-style-span" style="line-height: 1.4em;">One cool thing about the testing tool is that not only will it validate your hRecipe formated pages but also many other microformats including <a href="http://microformats.org/wiki/hcard" style="color: #de7008;">hCard</a> which is the the markup spec for representing personal </span><span class="Apple-style-span" style="line-height: 22px;">identification </span><span class="Apple-style-span" style="line-height: 18px;">and <a href="http://microformats.org/wiki/hatom" style="color: #de7008;">hAtom</a>, for blogs</span><span class="Apple-style-span" style="line-height: 1.4em;">.</span></div><div style="font-family: 'Trebuchet MS', Trebuchet, Verdana, sans-serif; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 16px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"><span class="Apple-style-span" style="line-height: 1.4em;"> Finally it you want to learn more about how Google interprets the hRecipe just take a look at their </span><a href="https://github.com/leeclarke/hRecipeHelper/wiki/Rich%20Snippets%20Testing%20Tool" style="color: #4183c4; line-height: 1.4em; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; outline-color: initial; outline-style: none; outline-width: initial; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-decoration: none;">Recipe help page</a><span class="Apple-style-span" style="line-height: 1.4em;">. If you cClick around a bit more and you'll find data element info for hCard as well. Happy microformatting, lets make a <span class="Apple-style-span" style="font-family: Verdana, sans-serif; line-height: 19px;">semantic</span> web that's easier to discover and put to use!</span></div>Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0tag:blogger.com,1999:blog-5332582024103750170.post-38224741262203063412011-03-17T10:08:00.003-04:002011-03-17T14:20:05.296-04:00hRecipeHelper - new Chrome ExtensionA little while back Google announced a new Recipe Search function that can search just recipes and then filter the results on various components in the recipe as well as cook time and calories. Now that's how you can rapidly dissolve the agonizing question of "What's for dinner?" into immediate action! To do this Google is relying on a nifty standard called microformats and specifically in this case the <a href='http://microformats.org/wiki/hrecipe'>hRecipe</a> format.<br />
<br />
What hRecipe provides is a standard of coding HTML which reveals to bots or other programs the exact meaning of the web pages content, specifically the data values it contains. As a programmer who has written website scraping programs more then once in the past, this sort of formatting is a wonderful and accessible way to publish data. Unfortunately HTML is not exactly everyone's best skill, in fact I am willing to bet that most people don't know HTML and would very much like to avoid ever knowing HTML. Honestly, those people are perfectly reasonable, why should they?<br />
<br />
I started thinking of people I know who are advanced computer users who simply don't have the time or interest in learning to write web pages and realized that while microformats are freaking awesome to a developer they are still a bit demanding to the cook that just wants to share her favorite recipes with the world. I saw this as a chance to give a little something back to the blogging world and perhaps selfishly get even more great recipes turning up in Google's search. I've written a nifty <a href="http://bit.ly/f9xRrl">Chrome Extension called hRecipeHelper</a> to help recipe bloggers easily post nice looking; hRecipe (and Google) compliant recipes by simply filling out a recipe card and copying the code into their favorite blogging site. All the site needs to do is support posting of basic HTML which Blogger, LiveJournal and most other blog platforms support.<br />
<br />
<a href="https://chrome.google.com/webstore/detail/cgpnljccbfcjhhekfnikhpllfjpjidlj"><br />
<div><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKlanDrIu0jTMrulQ8nL9LWRaBlrZFOpdG9rn9po8_KAxpB6izSxFVzvzcSfXYBPBQO7Mw7liZ0XetrtiBIrVBte4Fzuz-_0h89ZvigU-7yR5AtdrDABD9Hx3OKLaeQifgxRBee8LOjgd8/" ></div>Click to get it now!</a><br />
<br />
I hope this will help get even more recipes on the web and further the microformats cause.<br />
<img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh29LBpW5Ta5R1uR7YMcEPT3iJtM5NWWHwXx6-0qhLXg4TDMI4XqK4yjpPfx7NtIyCqkgWbmNfTifBHmjWY7Nr1DxbPJ0mzR3FuTTtLrJEgJWnXzsD-hpnYxV1Nc0mlQTrbo6i2rciyYIVH/">Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0tag:blogger.com,1999:blog-5332582024103750170.post-21798440858429370502011-03-08T10:21:00.002-05:002011-03-08T16:47:43.817-05:00The Garden Droid 1.0 UpdateI started thinking that if anyone wanted to know more about how my Garden Droid was working out they would be left wanting because I'm not posting the updates to this blog. My logic is that this is my Tech blog for programming and Electronics and since this project leans towards my more green and leafy interested I have posted the updates on my other <a href="http://greenscreenprojects.blogspot.com/">blog</a>. If your interested click on over and check out my other blog.<br />
<div><br />
</div><div>Yes, much could be debated on the logic behind 2 blogs... I debate it often but, hey it makes some logical since none the less so I stick with it.</div>Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0tag:blogger.com,1999:blog-5332582024103750170.post-61896529082763891612011-02-01T12:02:00.001-05:002011-02-15T13:57:26.139-05:00The Garden Droid 1.0<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">Well after many months in the lab toiling away with a soldering iron and C compiler I have finally gotten the first version of my Arduino project done! OK really It just took months to get the time to finish the project but that doesn't sound as impressive really.. This is my first Arduino project and first really large electronics project so mistakes were made but overall I'm really happy that it all works!</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">I really started the project way back in February of last year and a little over a week ago I planted the GardenDroids first crop, Spinach. I mostly chose spinach because I believe it will grow fairly well in the 75 degree room upstairs where the Droid has been parked as well as its ability to grow in partial sun which I am assuming is about what the LED lighting will produce.. tho I may be mistaking on that. We will see!</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">So what is a mini-greenhouse? Well in one sentence it's an electronically maintained greenhouse which can be maintained indoors and monitored from your web browser.</div><div class="separator" style="clear: both; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgAZBbVR0jnSRCzzkx5juxRhRtSgpdcXP9Vf4-PmZIlrRRb6Wpj1hvZknuAFy5FcU0b8t8gbrZTBr2r8R302uU-jfHjVKUOdQxvn7xN-zms9HTmT0SFRwwSK_3B4wWQT_S20m2BIQrHDaB/s1600/IMG_20110123_214931.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="239" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgAZBbVR0jnSRCzzkx5juxRhRtSgpdcXP9Vf4-PmZIlrRRb6Wpj1hvZknuAFy5FcU0b8t8gbrZTBr2r8R302uU-jfHjVKUOdQxvn7xN-zms9HTmT0SFRwwSK_3B4wWQT_S20m2BIQrHDaB/s320/IMG_20110123_214931.jpg" style="cursor: move;" width="320" /></a></div><div style="text-align: center;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span class="Apple-style-span" style="font-size: x-small;">This is my mini-greenhouse which I am calling The Garden Droid (you know like garden gnome?)</span></div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">What you see above is my Garden Droid which boasts a whopping 3 sq. ft. of well lit gardening space, soil moisture monitoring, temperature logging, LED grow lights and wireless transmission of data to my computer. Coming soon it will also be self watering, monitor humidity and ambient light as well as have an upgraded wireless protocol so it can transmit small pictures of the plant growth and support multiple Droids. </div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">Has I mentioned the website where I can monitor everything going on? Below is a quick snap of the web application which is used to monitor not just the Garden Droid plants but, your whole garden.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div class="separator" style="clear: both; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIOriNctsb1pSN4wF9MoZiQ4E3KuHXJRfhgu1AzM_T6M3JDxhz7Uq6wVnV0zV3cK-O-luYGdpAQ995p2oNja6q744uCkspVZ6Tue6Y9ko8NQd_6CzsQT7oJ5fs6GEBMpIShG5QO1LzrqYa/s1600/GardenDroidWeb.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIOriNctsb1pSN4wF9MoZiQ4E3KuHXJRfhgu1AzM_T6M3JDxhz7Uq6wVnV0zV3cK-O-luYGdpAQ995p2oNja6q744uCkspVZ6Tue6Y9ko8NQd_6CzsQT7oJ5fs6GEBMpIShG5QO1LzrqYa/s320/GardenDroidWeb.gif" style="cursor: move;" width="320" /></a></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">The whole project is open source and built on free software with open hardware that anyone can buy and build. I will be trickling out technical details and how to's in the following weeks for anyone interested and heck if my plants grow well I might even sell kits but, first let's see if I can get spinach to grow.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">Part of my motivation for this project was to be able to grow salads year round but here in FL most of the summer is not kind to regular spinach and other leafy greens, its just too darn hot and all my efforts have been a failure. I figured that since I am keeping my house artificially cool all summer and the temps would be great for growing greens, why not give it ago? I plan to eventually get this little box powered by a solar panel collecting dust int he garage as well then there will be a net gain of energy savings though just growing at home saves oil I would think.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">Anyway I am really excited about version 1 coming together and I am looking forward to the next version with the enhanced capabilities. To check out the code jump over to my <a href="https://github.com/leeclarke/TheGardenDroid">GitHub</a> where you can see the code for the Arduino, the data logging deamon and the full web app. I will be posting the real technical details just as soon as I can throw together some schematics and I'll keep you posted on the plants progress as well.</div>Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com1tag:blogger.com,1999:blog-5332582024103750170.post-15006866307778953532011-01-13T12:18:00.000-05:002011-01-13T12:18:06.550-05:00Those crazy Sparkfun guys<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz8guq0nM-fhpKJ3orcem1x2uGzwg5QEAuL0VAapO-X8XCZFzqoGmUeuZ17Z3be6OO4icKTYgIPFWmBR3hZoCBgXdiZzvGfdTtDnLoYom9CLmlPl2VEzNUZAjFnTrnQyAN4MDzurB-v4Cw/s1600/sflogo.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><br />
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz8guq0nM-fhpKJ3orcem1x2uGzwg5QEAuL0VAapO-X8XCZFzqoGmUeuZ17Z3be6OO4icKTYgIPFWmBR3hZoCBgXdiZzvGfdTtDnLoYom9CLmlPl2VEzNUZAjFnTrnQyAN4MDzurB-v4Cw/s1600/sflogo.gif" /></a></div><br />
<a href="http://www.sparkfun.com/">Sparkfun </a>is giving it away for free again this year! Last year their servers were so overloaded that I never got past the log-in page to try for the free goods, it was quite a bummer but a good lesson in server traffic. This year I am excited to say that I have managed to wait out the heavy server traffic and pick up a customer loyalty credit! :D I've been a member for 4 years so $40 worth of electronics gear will be great fun to have, I think I have an xbee mesh network coming up i my future, that should be fun when I add my GardenDroid and future home automation system all reporting back to my web server.<br />
<br />
Thanks Sparkfun! [I'm sure I'll go over the 40... ;)]Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0tag:blogger.com,1999:blog-5332582024103750170.post-67102975014256972982010-07-01T16:03:00.000-04:002010-07-01T16:03:01.025-04:00Reconfiguring Log4j at runtimeAt the start of my latest project I realized that I often take for granted the presence of log4j. For me it's just a given that it will be in my next app and that everyone already knows how to hack log4j for their own needs. Not so! <br />
<br />
While including Log4j in your app and calling debug is usually a given I think I'm one of a hand full of people that have actually looked over the JavaDocs to discover what else lgo4j can do for me. Hey it's OK, we all have deadlines and reading logger JavaDocs isn't exactly on the critical path right?<br />
<br />
If you haven't yet, I would encourage you to skim through the JavaDocs. For those who decline or are just too busy, I'd like to share a nice feature that will save you some serious time.<br />
<br />
So, how would you like to control the log4j Loggers in your application deployed to production or anywhere off of you local environment? More often then not you just don't get the option to leave debugging on in production since SysAdmins don't care much for verbose logging on their servers and rightly so since logs can grow rapidly if your a good user of debugging. What you really need is a logger admin page in your app where you can change the logging levels of configured loggers and add new, something like the following image.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Ksbj-fEY2uJOLIogz_4Qtwing2oVhnUwNnbvX7cPlqSIyao_ehL9t3oV_2i3XI5po9OrPrObfECaOk0VZ6xEPrIXjUgnFGqtexkocX1wN_n5vnmbM9tKAR0C0QdMYunTSZ6f9OUUjlFF/s1600/Manage+Application+Logger.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Ksbj-fEY2uJOLIogz_4Qtwing2oVhnUwNnbvX7cPlqSIyao_ehL9t3oV_2i3XI5po9OrPrObfECaOk0VZ6xEPrIXjUgnFGqtexkocX1wN_n5vnmbM9tKAR0C0QdMYunTSZ6f9OUUjlFF/s320/Manage+Application+Logger.png" /></a></div><div style="text-align: center;">(Note:Some package names were obscured to protect the innocent)</div><br />
In the above image you see a logger manager screen where you can change the logging level as well as a text box where you could enter a new Logger such as com.mycorp.myapp.dao or the full path of a specific class.<br />
<br />
So how's all this work? It pretty much comes down to Log4j Loggers and their Levels. First off is the Logger which in the case of Log4j can only be created by calling a static Factory method, Logger.getLogger(String name). If you have used Log4j before you are no doubt well acquainted with this method when your creating a logger for a specific Class. All instances of Logger are children of the RootLogger which always exists in log4j and can only be retrieved by calling Logger.getRootLogger(). This is how log4j can perform its logging at the class or package level and only print out messages at the root configured level such as ERROR which tends to be the default level.<br />
<br />
Speaking of Levels, all loggers are configured to log at a specific Level which is inherited from the parent logger such as the RootLogger or another logger in the family tree specified by package notation in the name. For example com.foo is a parent to com.foo.bar, If the root is configured at ERROR com.foo.bar would be at ERROR as well unless com, com.foo, or com.foo.bar have Loggers are configured at a different Level.<br />
Looking at the above screen shot you should be able recognize the Loggers and their assigned Levels. <br />
<br />
Now let me explain that form displayed above. At the top of the form is the Logger Name text box which allows you to create a new Logger. Lets say you want to create a logger for com.foo.bar.model.dao.MyDAO; you simply type that or some part of that package into the text box, select a level like DEBUG and click OK. The form updates itself and the new logger is displayed on the form. Logging for that level will be activated and you'll be able to see whats going on!<br />
<br />
After your done debugging whats wrong with MyDAO you might want to set its Logging Level back to ERROR, to do this simply change the Level in the drop-down and the form submits updating the Logger to its new Level. Easy real time management of Logger Levels!<br />
<br />
So enough theory and rambling, your here her for the code, I know you are! Let me walk you through the code now! Most of the example code is your general MVC stuff so I'll point out the overall structure and then point out the interesting parts in each class file. I am currently working with JSF for my views so this example has JSF in the view but should translate to a jsp very simply if desired.<b> </b><br />
<br />
<b>LoggerManager </b>which acts as the Controller of creating Loggers and <b>LoggerAdmin </b>a Form BackingBean which takes care of the view logic. In addition there are two supporting files, <b>ComparableLogger</b> and of course the html view.<br />
<br />
The LoggerManager acts as our service/controller layer which contains 2 aptly named methods, getLoggers() and addLogger(). The getter provides a controlled manner for the front end to retrieve the current log4j Loggers as a list of ComparableLoggers which extend the log4j Logger class (keep reading for details on ComparableLogger).<br />
<br />
<br />
<code>Enumeration<logger> loggers = LogManager.getCurrentLoggers();<br />
while (loggers.hasMoreElements())<br />
{<br />
Logger lgr = loggers.nextElement();<br />
if(lgr != null &amp;&amp; filterUnconfigured)<br />
{<br />
if(lgr.getLevel() != null)<br />
list.add(new ComparableLogger(lgr));<br />
}<br />
else<br />
{<br />
list.add(new ComparableLogger(lgr));<br />
}<br />
}<br />
Collections.sort(list);<br />
</code><br />
<br />
The code here is requesting the list of Loggers by making a call to the static LogManager.getCurrentLoggers(). This returns an Enumeration which includes items that have no Log level there by taking the default. Those set to default can be ignored, so we loop through the returned Loggers building a List of Loggers with a logging level and in the process converting them to ComparableLoggers for later sorting.<br />
<br />
Wow that you know how to get the active Loggers, you'll want to be able to add new ones! You have most likely done this before, simply pass the new Name of the logger, something like "com.foo.dao" and also include a logging Level in the form of an int constant defined in the Level class. Then create a new logger as you normally would.<br />
<br />
<br />
<code>...<br />
Logger newLog = Logger.getLogger(newLoggerName);<br />
Level newLevel = Level.toLevel(newLogLevel);<br />
newLog.setLevel(newLevel);<br />
...</code><br />
<br />
<br />
Well that is the meat of the code right there, pretty simple really! The rest of the code is, as I mentioned, display oriented code. I'll run though the other classes and their purpose real quick to help you with the full picture. The next class, <b>LoggerAdmin</b>, is referred to as a a Backing bean because it is a jova object representation of the view. Its responsibilities include containing data and managing view logic. You'll notice that it does some translating to provide logger levels for the UI as well as passing long the UI requests to the Service layer.<br />
<br />
The next object is <b>ComparableLogger </b>and as mentioned before it exists to provide a sortable wrapper for a Logger object. Finally there is the <b>manageLogger.xhtml</b> file used by the Faces framework to generate the view. Faces is far outside the scope of this posting but looking it over should give you a general idea of what your view ought to be doing.<br />
<br />
I have included all of the code below:<br />
<br />
<b>LoggerManager.java</b><br />
<code>package org.lee.twobytes.managelogger;<br />
<br />
import java.util.ArrayList;<br />
import java.util.Collections;<br />
import java.util.Enumeration;<br />
import java.util.List;<br />
<br />
import org.apache.log4j.Level;<br />
import org.apache.log4j.LogManager;<br />
import org.apache.log4j.Logger;<br />
<br />
/**<br />
* Provides access helpers for retrieving Logger information about the running<br />
* application.<br />
*<br />
* @author lclarke<br />
*/<br />
public class LoggerManager<br />
{<br />
/**<br />
* Lists only configured loggers in Log4j also sorts the results by name.<br />
*<br />
* @return<br />
*/<br />
public List getActiveLoggers()<br />
{<br />
return getLoggers(true);<br />
}<br />
<br />
/**<br />
* Lists ALL active loggers in Log4j also sorts the results.<br />
*<br />
* @return<br />
*/<br />
public List getAllActiveLoggers()<br />
{<br />
return getLoggers(false);<br />
}<br />
<br />
protected List<comparablelogger> getLoggers(boolean filterUnconfigured)<br />
{<br />
// Logger rootLogger = Logger.getRootLogger();<br />
ArrayList list = new ArrayList();<br />
<br />
Enumeration loggers = LogManager.getCurrentLoggers();<br />
while (loggers.hasMoreElements())<br />
{<br />
Logger lgr = loggers.nextElement();<br />
if (lgr != null && filterUnconfigured)<br />
{<br />
if (lgr.getLevel() != null)<br />
<br />
list.add(new ComparableLogger(lgr));<br />
}<br />
else<br />
{<br />
list.add(new ComparableLogger(lgr));<br />
}<br />
}<br />
Collections.sort(list);<br />
return list;<br />
}<br />
<br />
/**<br />
* Creates the new Logger<br />
*<br />
* @param newLoggerName<br />
* @param newLogLevel<br />
* @throws TINException<br />
*/<br />
public void addLogger(String newLoggerName, Integer newLogLevel)<br />
<br />
throws Exception<br />
{<br />
if (newLoggerName != null && newLogLevel != null)<br />
{<br />
Logger newLog = Logger.getLogger(newLoggerName);<br />
Level newLevel = Level.toLevel(newLogLevel);<br />
newLog.setLevel(newLevel);<br />
}<br />
else<br />
{<br />
throw new Exception("Logger information was invalid or null.");<br />
}<br />
}<br />
}</code><br />
<br />
<br />
<b>LoggerAdmin.java </b><br />
<code><br />
package org.lee.twobytes.backingbeans.admin;<br />
<br />
import java.util.ArrayList;<br />
import java.util.Collections;<br />
import java.util.List;<br />
<br />
import org.apache.log4j.Level;<br />
import org.apache.log4j.Logger;<br />
<br />
import org.lee.twobytes.model.NameValuePair;<br />
import org.lee.twobytes.model.ComparableLogger;<br />
import org.lee.twobytes.model.LoggerManager;<br />
import org.lee.twobytes.model.exceptions.TBException;<br />
<br />
/**<br />
* Provides access to Logger manager for the display and configuration of log4j<br />
* loggers.<br />
*<br />
* @author lclarke<br />
*/<br />
public class LoggerAdmin extends AdminBaseBean<br />
{<br />
<br />
private static final Logger logger = Logger.getLogger(LoggerAdmin.class);<br />
<br />
private static List<NameValuePair<String, Integer>> LogLevels2;<br />
static<br />
{<br />
LogLevels2 = new ArrayList<NameValuePair<String, Integer>>();<br />
LogLevels2.add(new NameValuePair<String, Integer><br />
(Level.WARN.toString(), Level.WARN.toInt()));<br />
LogLevels2.add(new NameValuePair<String, Integer><br />
(Level.TRACE.toString(), Level.TRACE.toInt()));<br />
LogLevels2.add(new NameValuePair<String, Integer><br />
(Level.DEBUG.toString(), Level.DEBUG.toInt()));<br />
LogLevels2.add(new NameValuePair<String, Integer><br />
(Level.ERROR.toString(), Level.ERROR.toInt()));<br />
LogLevels2.add(new NameValuePair<String, Integer><br />
(Level.FATAL.toString(), Level.FATAL.toInt()));<br />
LogLevels2.add(new NameValuePair<String, Integer><br />
(Level.INFO.toString(), Level.INFO.toInt()));<br />
LogLevels2.add(new NameValuePair<String, Integer><br />
(Level.OFF.toString(), Level.OFF.toInt()));<br />
<br />
Collections.sort(LogLevels2);<br />
}<br />
<br />
private LoggerManager loggerManager;<br />
private String currentLoggerName = null;<br />
private String newLoggerName = null;<br />
private Integer newLogLevel = Level.DEBUG.toInt();<br />
<br />
public String getCurrentLoggerName()<br />
{<br />
return currentLoggerName;<br />
}<br />
<br />
public void setCurrentLoggerName(String currentLoggerName)<br />
{<br />
this.currentLoggerName = currentLoggerName;<br />
}<br />
<br />
public List<ComparableLogger> getActiveLoggers()<br />
{<br />
return loggerManager.getActiveLoggers();<br />
}<br />
<br />
/**<br />
* Adds a new logger to the app for the specified classpath<br />
*/<br />
public String processAdd()<br />
{<br />
logger.debug("ADD LOGGER: name:" + this.newLoggerName + " Level:" + this.newLogLevel);<br />
try<br />
{<br />
loggerManager.addLogger(this.newLoggerName, this.newLogLevel);<br />
this.setInfoMessageByKey("label.settingsSaveSuccess", null);<br />
this.newLogLevel = Level.DEBUG.toInt();<br />
this.newLoggerName = null;<br />
}<br />
catch (TBException e)<br />
{<br />
this.setInfoMessageByKey("label.erroraddinglogger", null);<br />
}<br />
<br />
// this.notesHandler.notifyReload();<br />
// } catch (TBException e) {<br />
// this.handleSystemError("error updating config items", e);<br />
// } <br />
return null;<br />
}<br />
<br />
/**<br />
* Updates the logger that changed levels.<br />
* @return<br />
*/<br />
public String processUpdate()<br />
{<br />
if (this.currentLoggerName != null)<br />
{<br />
logger.debug("Updating logger: "+this.currentLoggerName);<br />
}<br />
return null;<br />
}<br />
<br />
/**<br />
* Drives display values for the dropdown<br />
*<br />
* @return<br />
*/<br />
public List<NameValuePair<String, Integer>> getLogLevels()<br />
{<br />
return LogLevels2;<br />
}<br />
<br />
public LoggerManager getLoggerManager()<br />
{<br />
return loggerManager;<br />
}<br />
<br />
public void setLoggerManager(LoggerManager loggerManager)<br />
{<br />
this.loggerManager = loggerManager;<br />
}<br />
<br />
public String getNewLoggerName()<br />
{<br />
return newLoggerName;<br />
}<br />
<br />
public void setNewLoggerName(String newLoggerName)<br />
{<br />
this.newLoggerName = newLoggerName;<br />
}<br />
<br />
public Integer getNewLogLevel()<br />
{<br />
return newLogLevel;<br />
}<br />
<br />
public void setNewLogLevel(Integer newLogLevel)<br />
{<br />
this.newLogLevel = newLogLevel;<br />
}<br />
}<br />
<br />
</code><br />
<br />
<b>ComparableLogger.java</b> <br />
<code>package org.lee.twobytes.managelogger.model.ComparableLogger;<br />
<br />
import org.apache.log4j.*;<br />
<br />
/**<br />
* I wanted to sort Loggers but, they aren't comparable... this takes care of<br />
* that. IT sorts on the Logger name by default.<br />
*<br />
*/<br />
public class ComparableLogger implements Comparable<br />
{<br />
private Logger logger = null;<br />
<br />
/**<br />
* Indicates that loggers should be sorted by Name only,<br />
* if set to false<br />
* then sort is done by Level then name.<br />
*/<br />
public static boolean orderByNameOnly = true;<br />
<br />
public ComparableLogger()<br />
{<br />
<br />
}<br />
<br />
public ComparableLogger(Logger logger)<br />
{<br />
this.logger = logger;<br />
}<br />
<br />
public Logger getLogger()<br />
{<br />
return logger;<br />
}<br />
<br />
public void setLogger(Logger logger)<br />
{<br />
this.logger = logger;<br />
}<br />
<br />
@Override<br />
public int compareTo(ComparableLogger lgrIn)<br />
{<br />
if (!orderByNameOnly &&<br />
logger.getEffectiveLevel().toInt() !=<br />
lgrIn.getLogger().getEffectiveLevel().toInt())<br />
{<br />
return (logger.getEffectiveLevel().toInt() <<br />
lgrIn.getLogger().getEffectiveLevel().toInt()) ? -1 : 1;<br />
}<br />
else return logger.getName().compareTo(lgrIn.getLogger().getName());<br />
}<br />
<br />
/**<br />
* Exposes the Loggers EffectiveLevel value to the front end for use in<br />
* form.<br />
*<br />
* @return<br />
*/<br />
public int getLevelInt()<br />
{<br />
return this.logger.getEffectiveLevel().toInt();<br />
}<br />
<br />
/**<br />
* Sets the loggers actual Level to provided value if it changed.<br />
*<br />
* @param newVal<br />
*/<br />
public void setLevelInt(int newVal)<br />
{<br />
if (logger.getEffectiveLevel().toInt() != newVal)<br />
this.logger.setLevel(Level.toLevel(newVal));<br />
}<br />
<br />
public String getLoggerName()<br />
{<br />
return this.logger.getName();<br />
}<br />
}</code><br />
<br />
<br />
<b>manageLogger.xhtml</b><br />
<br />
<br />
<code><br />
<?xml version='1.0' encoding='UTF-8' ?><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"<br />
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"<br />
xmlns:ui="http://java.sun.com/jsf/facelets"<br />
xmlns:f="http://java.sun.com/jsf/core"<br />
xmlns:h="http://java.sun.com/jsf/html"<br />
xmlns:a4j="http://richfaces.org/a4j"<br />
xmlns:rich="http://richfaces.org/rich"<br />
xmlns:t="http://myfaces.apache.org/tomahawk"<br />
xmlns:c="http://java.sun.com/jstl/core"><br />
<br />
<ui:composition template="../templates/template.xhtml"><br />
<ui:define name="pageTitle"><h:outputText value="#{msg['manageLogger.page.title']}"/></ui:define><br />
<ui:define name="pageSubTitle"><h:outputText value="#{msg['manageLogger.page.subtitle']}"/></ui:define><br />
<ui:define name="content"><br />
<h:form id="form"><br />
<br />
<br />
<rich:messages id="pageMessages" globalOnly="true" infoClass="rich-messages-info" errorClass="rich-messages-error"/><br />
<br />
<a4j:outputPanel id="loggerPanel"><br />
<table id="loggerTable" border="0" cellspacing="0" cellpadding="3"> <br />
<tr><br />
<td colspan ="2"><br />
<table><br />
<tr><td colspan="3"><b><h:outputLabel value="#{msg['label.newlogger']}" title="#{msg['label.newlogger']}" style="padding-top:6px;height:20px;"/></b></td></tr><br />
<tr><td colspan="3"><h:outputLabel value="#{msg['label.loggername']}:" title="#{msg['label.loggername']}" style="padding-top:6px;height:20px;"/></td><br />
<td><h:inputText id="newLoggerName" value="#{loggerAdmin.newLoggerName}" style="width:200px;"/></td><br />
<td><br />
<h:selectOneMenu id="newLogLevel" value="#{loggerAdmin.newLogLevel}" ><br />
<t:selectItems value="#{loggerAdmin.logLevels}" var="lvl" itemLabel="#{lvl.name}" itemValue="#{lvl.value}" /><br />
</h:selectOneMenu><br />
</td></tr><br />
<tr><td colspan="3"><a4j:commandButton id="add" action="#{loggerAdmin.processAdd}" style="width:75px; margin-right:6px;" value="#{msg['label.ok']}" reRender="loggerPanel"/></td></tr><br />
</table><br />
</td><br />
</tr><br />
<ui:repeat var="logger" value="#{loggerAdmin.activeLoggers}"><br />
<tr> <br />
<td align="right" valign="top"><h:outputLabel value="#{logger.logger.name}:" title="#{logger.logger.name}" style="font-size:150%;padding-top:6px;height:20px;"/></td><br />
<td align="left"><br />
<h:selectOneMenu id="level" value="#{logger.levelInt}" ><br />
<t:selectItems value="#{loggerAdmin.logLevels}" var="lvl" itemLabel="#{lvl.name}" itemValue="#{lvl.value}" /><br />
<a4j:support event="onchange" action="#{loggerAdmin.processUpdate}"><br />
<br />
</a4j:support><br />
</h:selectOneMenu> <rich:message for="level"/><br />
</td><br />
</tr><br />
</ui:repeat><br />
<br />
</table><br />
</a4j:outputPanel><br />
<br/><br />
<br />
</h:form><br />
<br />
</ui:define><br />
</ui:composition><br />
</html><br />
</code>Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0tag:blogger.com,1999:blog-5332582024103750170.post-58237870615185658252010-05-25T16:30:00.006-04:002011-03-18T09:49:06.123-04:00H2 Database, Junit and Sequence Problems.Running my Junit tests against a disposable local database has saved me a lot of trouble but, it has given me a couple of problems as well.<br />
<br />
I'm quite pleased with how I can bring up a fresh H2 database when I start an JUnit test. I have built a simple JUnit Abstract class which manages the H2 setup loading the DDL for my database followed by sql files that are specific to each unit test (The latter being called from an @BeforeClass method calls a parent method with the path of the unit tests sql inserts. This gives me the ability to seed the database with good test data against which I can run my tests. By doign this I ensure that the data in the database is always exactly what it should be while the test is running. If you have ever tried to run tests against a standing dev database you know it rapidly becomes populated with all sorts of junk from other developers testing or even your own code. With this approach your data is always in a known state, problem solved!<br />
<br />
I like H2 and its very useful but,I have come across a couple of oddities using while using it as part of my testing structure. First, while H2 is Oracle compliant if accessed with mode=oracle on the JDBC URL, there are still some DDL and SQL commands which aren't supported, to_date() being a big one. <br />
<br />
This is unfortunate since you also want to run your tests against a current database structure and if your in an agile environment with a new database in the works your DB is changing as often as your code! I got around that issue by building a Processor that simply converts all SQL into a compliant format discarding statements that are not usable. Thanks to this processor I can then dump DDL from Toad and simply load up the table export for testing. Nice!<br />
<br />
The other notable issue I encountered when implementing this testing framework is encountered when processing the seed data sql. The following simple insert statements creates unexpected SQL errors:<br />
<br />
<code>INSERT INTO MY_TABLE (id, some_field, some_other_field) VALUES(MY_TABLE_SEQ.nextval, 'value', 'value');</code><br />
<br />
Note the usage of the sequence call to nextval: MY_TABLE_SEQ.nextval<br />
<br />
For whatever reason this call simply doesn't work. You will receive a SQL error informing you of Primary Key violation requiring a unique id. Yea you SHOULD be inserting one from the sequence but, the H2 sequence is not incremented by the call and you get an old sequence number!<br />
<br />
The solution is really quite simple but, sent me on a bit of a hunt trying to figure out what was amiss. In an effort to save someone else a chunk of time here is the solution:<br />
<br />
<code>SELECT MY_TABLE_SEQ.nextval from DUAL;<br />
INSERT INTO MY_TABLE (id, some_field, some_other_field) VALUES(MY_TABLE_SEQ.currval, 'value', 'value');</code><br />
<br />
Told you it was simple! I almost feel silly for having spent so much time trying to debug my code! As long as you give H2 a chance to commit a sequence increment, the problem is solved. This works great for testing and solves my problem.<br />
<br />
It does imply that there could be issues in H2 if you are doing multiple user inserts and incrementing that sequence so if your in this sort of situation keep your eye out for issues. Fortunately it doesn't effect my local JUnits!Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com1tag:blogger.com,1999:blog-5332582024103750170.post-36178726007776391512010-05-21T11:26:00.000-04:002010-05-21T11:26:46.437-04:00Hey there!Since this is my first post it seems reasonable to jot down a quick comment about the purpose of my blog and what I hope to write here. I have a somewhat unhealthy interest in technology and software development, its what I do for a living and frequently what I do for fun. Because of this I get around a good bit and just want to share what I pick up along the way. In a more selfish light I want to record my experiences so I can go back a year later and check out what I learned because inevitably I'll need to know it a year after I forget it and have lost all my notes, assuming there were any notes!<br />
<br />
I have been hacking computers since I found out I could reprogram my favorite games on the Apple IIe back in the day. These days I am most interested in open source and Linux as well as just about any viable language I can use to make these technologies more fun. Additional technological fascinations include the Android platform and social media technologies. I get into the code but I also find the theoretical and the implications of new technology interesting and love to speculate.<br />
<br />
I will try to stay on target, in a topical since but, since I am focusing on "technology" that can include a smattering of Sci-fi discussion and other general topics of geekdom.<br />
<br />
Coming up shortly, H2 database, Facebook GraphAPI and a Log4j trick.Leehttp://www.blogger.com/profile/02420717967424628293noreply@blogger.com0