tag:blogger.com,1999:blog-369564132024-02-28T09:30:46.517-08:00Tech DiaryJava, JSP, Web Services using axis2, tcpmon with axis2, Hibernate, Excel vb scripting, Registry tweaks, Java decompilers, buddy spy, tor, privoxy, Flex, Active MQ, JMS, JExcel, j2ssh, xdoclet2, velocity, CXF webservice, AS3 Generator,JAX-WS, RMI, log4j, Lightstreamer, XSocket, AspectJ, JavaFX, JQuery, Ruby, Scala, Groovy, Play 2.0, LiftSacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.comBlogger70125tag:blogger.com,1999:blog-36956413.post-52338169642981111492014-05-06T18:35:00.001-07:002014-05-06T18:37:54.411-07:00Using camel FOP to convert text documents to PDFCamel is awesome. No doubts about it. While working on camel I encountered a requirement where I had to convert text files to PDF. After looking into camel components I found out about "FOP" (http://camel.apache.org/fop.html). You just need to add dependency and create the required route. Seems, simple at the beginning, looking at the few snippets on the website. But, to convert the text file to PDF you need to generate the XSL-FO that contains both the formatting instructions (xslt) and the real data (xml). While this is good when you know the documents that you need to generate, it is not that obvious how to generate/convert any random document text file to PDF. But fear not, there is a way. <br />Create a route that reads text documents from a directory, send it through a processor that creates the XSL-FO around the document, set the file name, author etc attributes, forward to the FOP component, then to the file system. The code is shown below and is well documented where necessary. <pre class="brush: java;">package com.bitourea.camel.routes;<br />import com.bitourea.camel.util.AppUtil;<br />import org.apache.camel.Exchange;<br />import org.apache.camel.Processor;<br />import org.apache.camel.builder.RouteBuilder;<br />import org.apache.camel.component.fop.FopConstants;<br /><br />public class TextToPdf extends RouteBuilder{<br /> @Override<br /> public void configure() throws Exception {<br /> String readDir = "c:/Temp/camel/textToPdf";<br /><br /> //read from directory, filter for text files<br /> from("file://"+readDir+"?noop=true&include=([a-zA-Z]|[0-9])*.(txt)")<br /> .routeId("textToPdf")<br /> .process(new Processor() {<br /> @Override<br /> public void process(Exchange exchange) throws Exception {<br /> final String body = exchange.getIn().getBody(String.class);<br /> final String fileNameWithoutExtension = AppUtil.getFileNameWithoutExtension(exchange);<br /> final String convertToXSLFOBody = AppUtil.getFilledXSLFO(body);<br /> exchange.getIn().setBody(convertToXSLFOBody);<br /> exchange.getIn().setHeader(Exchange.FILE_NAME, fileNameWithoutExtension + ".pdf");<br /> exchange.getIn().setHeader(FopConstants.CAMEL_FOP_RENDER + "author", "Shreyas Purohit");<br /> }<br /> })<br /> .to("fop:application/pdf")<br /> .to("file://" + readDir);<br /> }<br />}</pre><br />The AppUtil used in the above code is shown below. <br /><br /><pre class="brush: java;">package com.bitourea.camel.util;<br />import org.apache.camel.Exchange;<br /><br />public class AppUtil {<br /> public static final String EXT_DELIM = ".";<br /> private static final String fopMainTemplate = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +<br /> "\n" +<br /> "<fo:root xmlns:fo=\"http://www.w3.org/1999/XSL/Format\">\n" +<br /> "\n" +<br /> "<fo:layout-master-set>\n" +<br /> " <fo:simple-page-master master-name=\"A4\">\n" +<br /> " <fo:region-body margin=\"25pt\"/>\n" +<br /> " </fo:simple-page-master>\n" +<br /> "</fo:layout-master-set>\n" +<br /> "\n" +<br /> "<fo:page-sequence master-reference=\"A4\">\n" +<br /> " <fo:flow flow-name=\"xsl-region-body\">\n" +<br /> "#BLOCK_CONTENT" +<br /> " </fo:flow>\n" +<br /> "</fo:page-sequence>\n" +<br /> "\n" +<br /> "</fo:root>";<br /> private static final String fopBlockTemplate = " <fo:block font-family=\"Courier\" font-weight=\"normal\" " +<br /> "font-style=\"normal\" score-spaces=\"true\" white-space=\"pre\" linefeed-treatment=\"preserve\" " +<br /> "white-space-collapse=\"false\" white-space-treatment=\"preserve\" font-size=\"10pt\">#CONTENT</fo:block>\n";<br /><br /> public static String getFileNameWithoutExtension(Exchange exchange){<br /> String fileName = (String) exchange.getIn().getHeader(Exchange.FILE_NAME);<br /> return fileName.substring(0, fileName.indexOf(EXT_DELIM));<br /> }<br /><br /> public static String getFilledXSLFO(String content){<br /> return fopMainTemplate.replaceAll("#BLOCK_CONTENT", getXSLFOBlock(content));<br /> }<br /><br /> private static String getXSLFOBlock(String line){<br /> return fopBlockTemplate.replaceAll("#CONTENT", line);<br /> }<br />}</pre><br />If you want, you can externalize the template into xslt, and create required XML for the XSLT to generate the right XSL-FO. I find that overkill when all I want is to append the strings and forward to next route for all incoming documents. Also, note that I am using the monospace font Courier in the template since it maintains the formatting the best and I dont have to supply additional fonts(One of the 13 font family which are directly available with PDF readers- Helvetica, sans-serif, SansSerif, Times, Times Roman, Times-Roman, serif, any, Courier, monospace, Monospaced, Symbol and ZapfDingbats). The FOP component also support the XML configuration file that can be used to define/load the fonts from the file system if necessary that can be used instead. Change 'margin' in 'region-body' if you wish to increase or decrease the PDF document margin. For the text to be embedded as is in the PDF the following attributes are set, white-space=pre, linefeed-treatment=preserve, white-space-collapse=false, white-space-treatment=preserve. <br /><br />At the moment of this writing the FOP component supported loading the configuration file from classpath, but, did not support custom UriResolvers to be plugged in or provided a classpath resolver. In effect, we could not load the fonts defined in the XML FOP configuration from classpath and they must be present on the filesystem. <br /><br />This is one of the easiest way to convert text files to PDF documents. <br /><br /> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-89230593936713234322014-03-29T10:46:00.000-07:002014-03-29T10:48:37.517-07:00Releasing chrome extension for UptimeRobot monitor<span style="font-family: Verdana, sans-serif;">I have developed an opensource chrome browser extension to enable easy integration with <a href="https://uptimerobot.com/" target="_blank">Uptime Robot</a> API's and monitor server/monitor statuses using chrome browser. This extension helps one to see server stats and get notification while the chrome browser is on.</span><br />
<span style="font-family: Verdana, sans-serif;"><br /><b>Features</b></span><br />
<ul>
<li><span style="font-family: Verdana, sans-serif;">Server up and down desktop notifications.</span></li>
<li><span style="font-family: Verdana, sans-serif;">Immediate visible server up and down notifications in the extension browser action icon.</span></li>
<li><span style="font-family: Verdana, sans-serif;">Group monitors to see them separated in the extension display.</span></li>
<li><span style="font-family: Verdana, sans-serif;">Options and data stored using chrome storage sync API allowing it to be sync'ed between chrome's if signed in.</span></li>
<li><span style="font-family: Verdana, sans-serif;">Uses <a href="http://shreyaspurohit.github.io/jquery.plugin.uptimeRobotMonitor/">jquery.plugin.uptimeRobotMonitor</a> to provide a beautiful visualization of server status and statistics related to 1 day,7 day and all time server uptime percentages.</span></li>
<li><span style="font-family: Verdana, sans-serif;">Uses only Monitor API keys (not Account API keys) and hence trustable and secure.</span></li>
</ul>
<span style="font-family: Verdana, sans-serif;">Check for more new features and contribute to source at the <a href="http://bit.ly/1g9pVtF" target="_blank">project site</a>. Download by clicking on the image below.</span><br />
<span style="font-family: Verdana, sans-serif;"><br />
<br />
</span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://bit.ly/1mdSnjs" target="_blank"><span style="font-family: Verdana, sans-serif;"><img alt=" Chrome Webstore" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmoFfIci6QewqBi4NU7b3Lp0CMAkCmzbQ4yySm0bIBI78q9hnCiObPGa1CYYMqvCPTRaLCWdoeXLVdcIHvQxLQaut1velK4tMUdda_YBzn6lyU5ScJ6Yo55V7fghsx2oOX2P3CPg/s1600/webstore_m.png" height="90" width="320" /></span></a></div>
<span style="font-family: Verdana, sans-serif;"><br />
</span><br />
<div>
<br /></div>
SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-62107438587501294132014-03-20T07:44:00.001-07:002014-03-29T10:47:44.471-07:00Releasing my first android app "Unlock Alert"<div class="gmail_quote" style="background-color: white; color: #222222;">
<div dir="ltr">
<div>
<span style="font-family: Verdana, sans-serif;">A project I started to learn android ended up in a full fledged app that can be used by many. I am releasing this app today on Google Play Store. </span></div>
<div>
<span style="font-family: Verdana, sans-serif;">Visit <a href="http://bit.ly/1eRL4ZD" style="color: #1155cc;" target="_blank">website</a> to find out about the app and download it. Please remember to rate/comment it. The app has been in beta testing for some time now and is pretty stable. But since "things" happens, do let me know if it doesn't work or crashes on your device at <a href="http://bit.ly/1bBc5EN" target="_blank">google groups</a>. I would like to get together and debug/solve the issue.</span></div>
<div>
<span style="font-family: Verdana, sans-serif;">If you rather just download it, then here it is:</span></div>
<div style="font-family: arial, sans-serif; font-size: 13px;">
<br /></div>
<div style="font-family: arial, sans-serif; font-size: 13px;">
<div>
<div style="text-align: center;">
<a href="http://bit.ly/OBt3Iw" target="_blank"><img alt="Download From Play" height="69" src="https://blogger.googleusercontent.com/img/proxy/AVvXsEg2LfqtfBHHoODO1K3rc3nvn2y4bh5s3KEu4rfRBFS5c2QUiDPr9UPfHCE8H3wQSX2a7HnPlJnvWtnD9ouIYfDHCNH1FS9rD-lc4gd5ilhTVMqXmxzcB87ldkOHrFX53WRklCdnpbxFqoYtbByakgrZGpBIK__lBphQwtPWldk=s0-d-e1-ft" width="200" /></a></div>
<div>
</div>
</div>
</div>
</div>
</div>
SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-15615866676874483542013-11-29T06:31:00.000-08:002013-11-29T06:31:02.316-08:00Review of Getting Started with SBT for Scala (Packt-Pub)<span style="font-family: Verdana, sans-serif;">I got the opportunity to review a book in the scala domain published by Packt on SBT. I am very glad that so many books are coming up in this domain. SBT is one of the most important components in scala infra. Most, if not all, of the projects use SBT as the build tool in the scala projects. </span><br />
<span style="font-family: Verdana, sans-serif;"><br /></span>
<span style="font-family: Verdana, sans-serif;">The book itself is very well written and has a good flow through out. The installation, running, and structure of SBT has also been explained. There is a very nice history of evolution of all the relevant build tools that are in the industry. I like chapter 1 esp for the way it quickly lets you get into code and start writing simple build scripts and to become aware of the minimum commands in the SBT that you ought to know. The chapter 1 provides intro to run, compile, test, scala REPL with real code examples that are easy to comprehend. One of the most important concepts of SBT has been clearly mentioned- Blank lines act as delimiter in a .sbt file. There are many operators in SBT like :=, +=, ++= etc which can be used in build definitions which has been covered clearly. There are nice tips which are present through out the book. Maven, Ivy has also been introduced. Ivy and its work lifecycle has been explained a bit in detail. I can live without that. Ivy itself is a big topic that can be read separately. The different types of scopes has also been covered. There is also a good list of commands/types in SBT which is presented.</span><br />
<span style="font-family: Verdana, sans-serif;"><br /></span>
<span style="font-family: Verdana, sans-serif;">The version's specified in the dependency need not be a single version as SBT is based on Ivy. There can be more constraints on it like 1.0.+ means select latest in 1.0.x in the dependency. This has been mentioned but no adequate examples has been given. I think this could have easily been added in one or two lines in a beginners book. This is a pretty useful feature. Next, scalaz ex in the full build definition is hardly understood. It should have been expanded a bit more. The full build definition chapter itself could have been explained a bit more. It is one of the key functionalities that allows a lot of flexibility in SBT. The multi project builds in that chapter has been explained well though.</span><br />
<span style="font-family: Verdana, sans-serif;"><br /></span>
<span style="font-family: Verdana, sans-serif;">Overall, I like the book. It gets you started in SBT as the title states pretty quickly. No book can replace your own research and work. This book can definitely serve as a quick reference to common commands, operators etc when in need after you have learned and worked with SBT.</span><br />
<span style="font-family: Verdana, sans-serif;"><br /></span>
<span style="font-family: Verdana, sans-serif;">The book is available on packt-pub <a href="http://www.packtpub.com/getting-started-with-sbt-for-scala/book" target="_blank">here</a>.</span><br />
<br />SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-57520248760366487732013-10-29T09:37:00.001-07:002013-10-29T09:39:02.004-07:00Apache Camel JMX with remote jconsole using host : portBy default when JMX is enabled in apache camel, it binds such that you will have to use a rather encrypted path in jconsole URI for connecting remotely. From there documentation- <pre class="brush: java;">service:jmx:rmi://localhost:<connectorPort>/jndi/rmi://localhost:<registryPort>/<serviceUrlPath><br />or<br />service:jmx:rmi://localhost:2000/jndi/rmi://localhost:1099/jmxrmi/camel</pre><br /><br />You will not be able to connect using localhost:1099 in the jconsole. This was very important to me as I was not just using jconsole but zabbix to gather data from JMX. In case of zabbix the only option that is available was host:port. The reason this does not work is the serviceUrlPath which by default in camel is customized to /jmxrmi/camel instead of the more known /jmxrmi. The fix is easy as show below-<br /><br /><pre class="brush: xml;"><camelContext id="camelAppContext" xmlns="http://camel.apache.org/schema/spring"><br /> <propertyPlaceholder id="properties"<br /> location="classpath:common.properties"/><br /> <jmxAgent id="camelAppJmxAgent" registryPort="{{jmx.registry.port}}" connectorPort="{{jmx.connector.port}}"<br /> createConnector="true" serviceUrlPath="/jmxrmi"/><br /></camelContext></pre><br />Or by using the JMX from –D options.<br /><pre class="brush: java;">-Dorg.apache.camel.jmx.serviceUrlPath=/jmxrmi</pre><br />With this configuration you can connect using host:port (localhost:1099) to JMX using jconsole. SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-44852564950048150942013-10-21T20:58:00.001-07:002013-10-21T21:01:51.388-07:00Liftweb 2.4 and Jamon 2.74 request monitoringI like Jamon as a library for collecting performance statistics. It is well established and provides sufficient types of statistics. One of the favorites statistics of mine being the frequency distribution of response times. Using Jamon in simple but with Lift 2.4 it was little challenging. The Jamon MonitorFactory returns an instance of Timing Monitor that maintains the statistics. For the collection to occur the same instance must be accessed to call stop after method execution. i.e- <pre class="brush: java;">Monitor monitor = MonitorFactory.start("key1");<br />//Code whose performance needs to be measured<br />//<br />//<br />monitor.stop();</pre><br />The problem with older version of Liftweb is there is no way of wrapping every request such that this monitor can be used. The S.addAround can be used with LoanWrapper but this gets executed multiple times as described <a href="https://groups.google.com/forum/#!msg/liftweb/w8BqoJTCrq8/BjV241XWN6kJ" target="_blank">here</a>. As from the discussion from that thread you can see that a common LoanWrapper surrounding all requests would be (or has already been) added in Liftweb. But, if you can not upgrade the Liftweb to newer version then here is the solution. The idea is to use the LiftRules onBeginServicing and onEndServicing. But note that we need to use the same instance of Monitor created in onBeginServicing method. The request/response lifecycle in liftweb is described very well <a href="http://exploring.liftweb.net/master/index-9.html" target="_blank">here</a>. This will catch both Statefull GET and Ajax requests. The code below uses the setAttribute method of the original HHTPServletRequest to save and retrieve the Monitor between methods. Have fun with scala, liftweb and jamon!<br /><pre class="brush: scala;">//Boot.scala<br />def boot {<br /> val labelPrefix: String = "LIFT_WEB_HTTP."<br /> val monitorInReq: String = "monitorInReq"<br /> val extensionsNotToMonitor = List(".jpg", ".png", ".jpeg", ".ico", ".gif", ".bmp")<br /><br /> def pathFilter = {<br /> req: Req =><br /> req.path.wholePath.takeRight(1) match {<br /> case x :: _ if (extensionsNotToMonitor.contains(x.substring(x.lastIndexOf(".")))) => None<br /> case _ => Some(req.path.wholePath.mkString("/"))<br /> }<br /> }<br /><br /> LiftRules.onBeginServicing.append((req: Req) => {<br /> val label: Option[String] = pathFilter(req)<br /> if (label.isDefined) {<br /> val start: Monitor = MonitorFactory.start(labelPrefix + label.get)<br /> S.containerRequest.map(r => (r.asInstanceOf[HTTPRequestServlet]).req.setAttribute(monitorInReq, start))<br /> }<br /> })<br /><br /> LiftRules.onEndServicing.append((req, respBox) => {<br /> S.containerRequest.map(r => {<br /> val attribute: AnyRef = (r.asInstanceOf[HTTPRequestServlet]).req.getAttribute(monitorInReq)<br /> if (null != attribute) {<br /> attribute.asInstanceOf[Monitor].stop()<br /> }<br /> })<br /> })<br />}</pre> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-73876664735214137442013-06-16T00:14:00.001-07:002013-06-16T00:19:31.768-07:00SoapUI: Inserting an external XML node dynamically into an existing XML Response using XmlHolder<p><strong>The Problem:</strong> I have a Soap Response, and I have to modify that xml such that I insert a new XML node into response. The new XML is a string that I can declare in a groovy script or I can retrieve from a property.</p> <p>Few exceptions I encountered while trying to solve the problem: CData missing, can not add external document, Can not add groovy Node class to java Node class, viceversa and etc etc etc</p> <p>Hints to solve the problem on internet: There are many websites that provides sample which helps to solve a particular case, but could not exactly find the one I present here. The most useful one was here "<a href="http://siking.wordpress.com/2012/01/06/dynamically-create-elements-in-a-soapui-request/">http://siking.wordpress.com/2012/01/06/dynamically-create-elements-in-a-soapui-request/</a>" where in the coments he gives sample related to owner doc and importing into it.</p> <p><strong>Details:</strong></p> Lets say you have a soap request and response in a test step of a test case. The body of the response is like below. <pre class="brush: xml; ruler: true; collapse: true;"><Traveler xmlns="http://www.example.com/schemas"><br /> <Customer BirthDate="2001-01-01"><br /> <Person Language="EN-US"><br /> <Title>MR</Title><br /> <FirstName>SHRE</FirstName><br /> <MiddleName>NOWVERYMUCH</MiddleName><br /> <LastName>WANTED</LastName><br /> </Person><br /> <Email Type="HOM" Address="notvalid@notvalid.com"/><br /> </Customer><br /></Traveler></pre><br />And I want add a new XML node Payment to Customer. Lets say his payment information like credit card number which is an XML fragment like below. Later I will add the card number to it.<br /><br /> <pre class="brush: groovy; ruler: true; collapse: true;">def pay="""<Payment><br /> <Card Type="INTERNATIONAL" Vendor="VISA" Number=""><br /> <Name><br /> Huntsville Townhall<br /> </Name><br /> <Issuer Name="Bank of Nowhere"/><br /> </Card><br /></Payment>"""</pre><br />Create a new property transfer from Traveler xml in the Soap Response to copy the Customer node to a TestCase property "customer". You can do that by using the below configuration in a new Propperty transfer step after the Soap RQ/RS step.<br /><br /> <pre class="brush: groovy; ruler: true; collapse: true;">Source: SoapRequestStepName Property: Response<br />declare namespace ns1='http://www.example.com/schemas';<br />//ns1:Traveler//ns1:Customer<br />Target: TestCaseName Property: nodeCustomer</pre><br /><br />Add a new test step with groovy script called updatePaymentInXML. First step is to create a XmlHolder for the source XML Traveler and extract Customer node out of it.<br /> <pre class="brush: groovy; ruler: true; collapse: true;">def getXmlHolder(){<br /> def groovyUtils = new com.eviware.soapui.support.GroovyUtils( context )<br /> def nodeCustStr=testRunner.testCase.getProperty("nodeCustomer").getValue()<br /> log.info "Pre Update: " + nodeCustStr<br /> def holder = groovyUtils.getXmlHolder(nodeCustStr)<br /> holder.namespaces["ex"] ="http://www.example.com/schemas" <br /> return holder<br />}</pre><br />Next lets get the Customer node from it as a Node.<br /><br /> <pre class="brush: groovy; ruler: true; collapse: true;">def holder = getXmlHolder()<br />def nodeCustomer = holder.getDomNode("//ex:Customer")</pre><br />Next lets insert payment in the customer node.<br /><br /> <pre class="brush: groovy; ruler: true; collapse: true;">def insertPaymentInCustomer(holder, nodeCustomer, pay){<br /> def ownerDoc=nodeCustomer.getOwnerDocument()<br /> def payHolder=new com.eviware.soapui.support.XmlHolder(pay)<br /> payHolder.namespaces["ex"] ="http://www.example.com/schemas"<br /> def nodePay = payHolder.getDomNode("//ex:Payment")<br /><br /> def importedNodePay=ownerDoc.importNode(nodePay, true)<br /> nodeCustomer.appendChild(importedNodePay) <br /> holder.updateProperty()<br /> log.info "Post Update: " + holder.getXml()<br />}<br />insertPaymentInCustomer(holder, nodeCustomer, pay)</pre><br />Things to note in the above snippet is that we get the owner document and import the newly extracted Payment xml node from the xml string using a new XmlHolder instance. Lets add the card number to it.<br /><br /> <pre class="brush: groovy; ruler: true; collapse: true;">def updateCCNumberInHolder(holderIn, ccNumberIn){<br /> holderIn["//ex:Card/@Number"]=ccNumberIn<br /> holderIn.updateProperty()<br /> testRunner.testCase.setPropertyValue("nodeCustomerUpdated",holderIn.getXml())<br /> log.info "Post Update: " + holderIn.getXml()<br />}<br />updateCCNumberInHolder(holder, testRunner.testCase.getPropertyValue("ccNumber"))</pre><br />Now, the holder.getXml() gives you the latest XML with updated Payment and cc number in it. Test case property nodeCustomerUpdated has the all updated XML that you can use. SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com1tag:blogger.com,1999:blog-36956413.post-36708653006296261692013-04-04T19:30:00.002-07:002013-04-04T19:33:12.088-07:00Instant Lift web application how to (PACKT-PUB book) reviewI would categorize myself as advanced beginner as I know about liftweb/scala and have built applications for my company. I am not a advanced user and not an expert who has gone through the source of the liftweb. I state this because this allows you to gauge my level before deciding to buy the book. I rate the <a href="http://www.packtpub.com/lift-web-applications/book">book</a> as excellent for a beginner. The book starts with advantage of liftweb, setting up scala, setting up sbt along with using sbt basics, setting up eclipse environment, boot.scala and so on and on. The author intends the book for a beginner as he even explains for example what is a servlet container, and how JNDI works. Lift snippets, CSS selector bindings, embed tags, sitemap, and so many more concepts are explained in simple words. Let me be honest and say that I have gone through around 70 pages of the 96 page book. I like the book as it provides a very good starting point for everyone to start learning lift. So, back to my criteria for the review:<br /><br />1. It should be easily understandable for the beginners and must be useful for advanced users as quick reference. <br /><br />The author is really good in explaining the concepts in very simple words. The book can be used by advanced users as quick reference like google. I would provide a score of 4/5 for this.<br /><br />2. Must be more than the many available tutorials you find on the web about scala/lift technologies. <br /><br />Definitely this is true since there is flow through the topics introduced and you are never overwhelmed. The author covers a lot of concepts needed to build an application E2E. 5/5.<br /><br />3. Should not beat around the bush and get to the point quickly, which is develop a application using scala and liftweb. <br /><br />You will be up and running in no time. 5/5<br /><br />4. The most important thing would be, I should be able to build a simple yet useful application from end to end using the knowledge in the book and by expanding on it. <br /><br />I did not have time to build an application based only on the knowledge in this book. But, I feel the book covers many topics that I have used in my app development. This book definitely will help to build application E2E. Always remember that a book can spoon feed you only to a certain level after which you need to pickup by reading api docs, forums etc.<br /><br />5. Last but not least, it should encourage the users to proceed with using Liftweb and learn more about the awesome framework and the unique design approach taken by that framework at more depth and by themselves.<br /><br />The book provides relevant links when appropriate. It is simple enough to encourage anyone scared of entering the world of liftweb. Do note that I feel it is more easy to understand the book and concepts if you are well versed in scala. 4/5<br /><br />In summary, I like the book and would thank Pack Pub for giving me an opportunity to read and comment on it.SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-29310532151081844092013-03-16T13:20:00.001-07:002013-03-16T13:20:19.273-07:00Jquery Browser Select Dialog PluginI have been developing my website, <a href="http://www.bitourea.com/">http://www.bitourea.com</a> using jquery and in the process have used certain jquery components that are not compatible with IE version less than 9. I started searching for a jquery plugin that shows a dialog when browser is less than IE 9, but could not find one on the internet. I decided to write one highly configurable plugin using which you can show a dialog on website based on your requirements suggesting users new better browser to upgrade to. The plugin is hosted on github and is available under the liberal MIT license at <a href="https://github.com/shreyaspurohit/jquery.plugin.browserSelect">https://github.com/shreyaspurohit/jquery.plugin.browserSelect</a> . The plugin can be used to show the dialog not just when it is IE, but when it is IE, Firefox, Chrome, Safari, iCab of the many supported browsers. The dialog can be customized with different backgrounds, different upgrade browser suggestions etc. The github <a href="https://github.com/shreyaspurohit/jquery.plugin.browserSelect" target="_blank">URL</a> has the detailed description of how to use the plugin. To ease users in configuring their desired dialog with the required configuration I have developed the website <a href="http://browserselect.bitourea.com/">http://browserselect.bitourea.com</a> hosted on github pages <a href="http://shreyaspurohit.github.com/jquery.plugin.browserSelect/" target="_blank">here</a>. The same website is present in the example folder in the downloaded zip <a href="https://github.com/shreyaspurohit/jquery.plugin.browserSelect/archive/master.zip" target="_blank">here</a> along with the real plugin. I plan to include this plugin on all my websites and show dialog to any internet users still on the age old browsers especially IE 8 and below or the users using non-standard browser. One of the main reason of increase in development cost and time is due to the fact that we end up testing and supporting all the major older version of browser which are inherently buggy and non-standard compliant. Hopefully, this plugin will help some number of these users in migrating to newer browsers.SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-68669330492294081532013-02-28T19:35:00.001-08:002013-02-28T19:52:19.037-08:00Liftweb bookI was recently contacted by PACKT Publishing to do a review on their book about lift web framework. The book present <a href="http://www.packtpub.com/lift-web-applications/book" target="_blank">here</a> is authored by Torsten Uhlmann and is intended for beginners. Since, I like to read technical books and have read many in the past including a few of the reference manuals, I decided to say yes and have a go at it.<br />
This is what I plan to base my reviews upon.<br />
<ul>
<li>It should be easily understandable for the beginners and must be useful for advanced users as quick reference. </li>
<li>Must be more than the many available tutorials you find on the web about scala/lift technologies. </li>
<li>Should not beat around the bush and get to the point quickly, which is develop a application using scala and liftweb. </li>
<li>The most important thing would be, I should be able to build a simple yet useful application from end to end using the knowledge in the book and by expanding on it. I have not yet decided the application, but it will be something that I plan to showcase when my current projects - an application (not yet released) using nodejs/mongodb stack and a website for myself using jquery/html that indexes all my projects - are completed to a beta level release.</li>
<li>Last but not least, it should encourage the users to proceed with using Liftweb and learn more about the awesome framework and the unique design approach taken by that framework at more depth and by themselves.</li>
</ul>
<div>
The good news and the part that excites me is the book <a href="http://www.packtpub.com/lift-web-applications/book" target="_blank">website</a> claims to have the "Get the Job done" approach.</div>
<div>
Btw, I believe that all technologies are awesome and have there specific usage. Point to note though is, I like liftweb framework and I am a strong proponent of the same and I have used it for real world production deployed application during my work at Sabre Holdings. But this has nothing and has no relation to the results of my review which is strictly about the book and not the liftweb in itself. I will probably take couple of weeks before I write the review as I am working hard at my work place and involved in two other projects that I mentioned above along with my other commitments.</div>
SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-11772586213723305442012-10-11T17:42:00.001-07:002012-10-11T19:18:42.731-07:00Performance Optimization in Lift framework when using Surround and Embed Tags<p>I worked on a Lift 2.4 web application that used lot of Surround and Embed tags in HTML files. Looking in the LiftSession we can see that lift repeats the merging process again for every request received. This is not required and significant performance can be gained when we cache a merged HTML template and process that instead of evaluating "surround" and "embed" tag on every request. If you enable TemplateCache, then this new cache below will act like a cache one level up in the hierarchy. Note that its a good idea to enable/use this cache only in production mode to not to hinder development activities.</p> <p>There are multiple ways to implement this. The one I will explain here is by extending the LiftSession class and overriding the processTemplate method. But, if you have a custom content handler DispatchPF appended to lift dispatch, and you do some more stuff before calling the processTemplate yourself then you can add this caching in this class too. The code is very different when compared to the first approach and will try to provide at the end.</p> <p>First thing to do is to define a synchronized cache that is used to cache the processed templates. The code below is self explanatory. Note that most of the code to follow is copied from Lift and modified to serve the desired purpose.</p> <pre class="brush: scala; ruler: true;">/**<br /> * A synchronized cache that handles the caching of Processed Templates in production mode only. The strategy used<br /> * is Least Recently Used caching.<br /> */<br />object ProcessedTemplateCache{<br /> private val cache : LRU[String, NodeSeq] = new LRU(50)<br /><br /> def get(key: String): Box[NodeSeq] =<br /> if(Props.productionMode)<br /> cache.synchronized {<br /> cache.get(key)<br /> }<br /> else Empty<br /><br /><br /> def set(key: String, node: NodeSeq): NodeSeq =<br /> if(Props.productionMode)<br /> cache.synchronized {<br /> cache(key) = node<br /> node<br /> }<br /> else node<br /><br /> def has(key:String):Boolean =<br /> if(Props.productionMode)<br /> cache.synchronized {<br /> cache.contains(key)<br /> }<br /> else false<br /><br /><br /> def delete(key: String) {<br /> if(Props.productionMode)<br /> cache.synchronized(<br /> cache.remove(key)<br /> )<br /> }<br />}</pre><br />We need to split a NodeSeq to its components to use it in the CustomLiftSession to be defined later. This is done using pattern matching in scala. <br /><br /><pre class="brush: scala; ruler: true; collapse: true;">/**<br /> * A decompressor that expands a Node to its characteristics.<br /> * Ther order being - element, kids, isLazy, attrs, snippetName<br /> */<br />private object CachedSnippetNode {<br /> def unapply(baseNode: Node): Option[(Elem, NodeSeq, Boolean, MetaData, String)] =<br /> baseNode match {<br /> case elm: Elem if elm.prefix == "lift" || elm.prefix == "l" => {<br /> Some((elm, elm.child,<br /> elm.attributes.find {<br /> case p: PrefixedAttribute => p.pre == "lift" && (p.key == "parallel")<br /> case _ => false<br /> }.isDefined,<br /> elm.attributes, elm.label))<br /> }<br /> case _ => {<br /> None<br /> }<br /> }<br />}</pre><br />Now, lets extend the LiftSession class. Note that, many methods in LiftSession is private and hence not accessible. The only way around the problem is by pulling them in the new CustomLiftSession implementation. So, here the version is very important. The code below uses the LiftSession from 2.4 codebase. Lift 2.5 has modification/extensions to this. I understand this as a bad idea as the methods pulled in from the LiftSession class may be modified in future and HAS to be synched up manually when required, but there is not much we can do using this approach due to private modifiers in the LiftSession class. Note that all the code is written in package net.liftweb.http. <br /><br /><pre class="brush: scala; ruler: true; collapse: true;">class CustomLiftSession(_contextPath: String, uniqueId: String,<br /> httpSession: Box[HTTPSession]) extends LiftSession(_contextPath, uniqueId, httpSession){<br /> //This val is copied as is from the super class, as it is private and not accessible from here.<br /> private object overrideResponseCode extends TransientRequestVar[Box[Int]](Empty)<br /><br /> //This val is copied as is from the super class, as it is private and not accessible from here.<br /> private val fullPageLoad = new ThreadGlobal[Boolean] {<br /> def ? = this.box openOr false<br /> }<br /><br /> /**<br /> * This method is copied as is from the super class, as method is private and not accessible from here.<br /> *<br /> * @param path The path to parse<br /> * @param session The session in which this is invoked<br /> * @return<br /> */<br /> private[http] def findVisibleTemplate(path: ParsePath, session: Req): Box[NodeSeq] = {<br /> val tpath = path.partPath<br /> val splits = tpath.toList.filter {<br /> a => !a.startsWith("_") && !a.startsWith(".") && a.toLowerCase.indexOf("-hidden") == -1<br /> } match {<br /> case s@_ if (!s.isEmpty) => s<br /> case _ => List("index")<br /> }<br /> Templates(splits, S.locale)<br /> }<br /><br /> /**<br /> * This method is responsible to do preProcessing cachedSurround and embed tags. Only these tags and any<br /> * of the same tags in its child nodes are processed. No other snippets are evaluated. The reason to do this<br /> * is to cache the merged pages one level up so that the merging "surround" and "merge" does not<br /> * occur every time.<br /> *<br /> * @param page The page name if any.<br /> * @param xhtml The page as an XML<br /> * @return<br /> */<br /> def preProcess(page:String, xhtml:NodeSeq): NodeSeq = {<br /> def processSurroundAndInclude1(page: String, in: NodeSeq): NodeSeq = {<br /> in.flatMap {<br /> case Group(nodes) =><br /> Group(processSurroundAndInclude1(page, nodes))<br /> //Only process cachedSurround and embed snippets<br /> case CachedSnippetNode(element, kids, isLazy, attrs, snippetName)if(snippetName == "cachedSurround" || snippetName == "embed") =><br /> S.doSnippet(snippetName) {<br /> S.withAttrs(attrs) {<br /> processSurroundAndInclude1(page,<br /> NamedPF((snippetName,<br /> element, attrs,<br /> kids,<br /> page),<br /> liftTagProcessing))<br /> }<br /> }<br /><br /><br /> case v: Elem =><br /> Elem(v.prefix, v.label, v.attributes,<br /> v.scope, processSurroundAndInclude1(page, v.child): _*)<br /><br /> case pcd: scala.xml.PCData => pcd<br /> case text: Text => text<br /> case unparsed: Unparsed => unparsed<br /><br /> case a: Atom[Any] if (a.getClass == classOf[Atom[Any]]) => new Text(a.data.toString)<br /><br /> case v => v<br /> }<br /> }<br /> processSurroundAndInclude1(page, xhtml)<br /> }<br /><br /> /**<br /> * The custom process template method that tries to use the cached preprocessed lift template, if not then caching one for the first time.<br /> * Every successive process template will use the merged template from the cache. This decreases one level of processing on<br /> * every request.<br /> *<br /> * @param template The real unprocessed template<br /> * @param request The request that wants the processed template<br /> * @param path The path to search for the template<br /> * @param code Override response code if any<br /> * @return<br /> */<br /> override def processTemplate(template: Box[NodeSeq], request: Req, path: ParsePath, code: Int): Box[LiftResponse] = {<br /> overrideResponseCode.doWith(Empty) {<br /> (template or findVisibleTemplate(path, request)).map {<br /> xhtml =><br /> fullPageLoad.doWith(true) {<br /> val path = S.request.get.path.wholePath.mkString("/")<br /> //Phase 0: Preprocess cachedSurround and embed or get the preprocessed page from the cache<br /> val preXML: NodeSeq = if(ProcessedTemplateCache.has(path))ProcessedTemplateCache.get(path).get<br /> else ProcessedTemplateCache.set(path, preProcess(PageName get, xhtml) ) <br /> //<br /> //---- Everything below here is as in the real Lift 2.4 codebase.<br /> //<br /> // allow parallel snippets<br /> // Phase 1: snippets & templates processing<br /> val rawXml: NodeSeq = processSurroundAndInclude(PageName get, preXML)<br /> // Make sure that functions have the right owner. It is important for this to<br /> // happen before the merge phase so that in merge to have a correct view of<br /> // mapped functions and their owners.<br /> updateFunctionMap(S.functionMap, RenderVersion get, millis)<br /><br /> // Clear the function map after copying it... but it<br /> // might get some nifty new functions during the merge phase<br /> S.clearFunctionMap<br /><br /> // Phase 2: Head & Tail merge, add additional elements to body & head<br /> val xml = merge(rawXml, request)<br /><br /> // But we need to update the function map because there<br /> // may be addition functions created during the JsToAppend processing<br /> // See issue #983<br /> updateFunctionMap(S.functionMap, RenderVersion get, millis)<br /><br /> notices = Nil<br /> // Phase 3: Response conversion including fixHtml<br /> LiftRules.convertResponse((xml, overrideResponseCode.is openOr code),<br /> S.getHeaders(LiftRules.defaultHeaders((xml, request))),<br /> S.responseCookies,<br /> request)<br /> }<br /> }<br /> }<br /> }<br />}</pre><br />Now, lets define a custom snippet dispatcher for a new tag "cachedSurround" that will be used instead of the "surround" tags in the HTML templates. <br /><br /><pre class="brush: scala; ruler: true; collapse: true;">/**<br /> * The snippet dispatcher that handles the "cachedSurround" lift tag.<br /> */<br />object CachedSurround extends DispatchSnippet {<br /><br /> def dispatch : DispatchIt = {<br /> case _ => render _<br /> }<br /><br /> def render(kids: NodeSeq) : NodeSeq =<br /> (for {<br /> ctx <- S.session ?~ ("FIX"+"ME: Invalid session")<br /> req <- S.request ?~ ("FIX"+"ME: Invalid request")<br /> } yield {<br /> WithParamVar.doWith(Map()) {<br /> val mainParam = (S.attr("at") openOr "main", ctx.asInstanceOf[CustomLiftSession].preProcess(PageName.get, kids)) //Do preprocess only, do not evaluate snippets right now<br /> val paramsMap = WithParamVar.get + mainParam<br /> ctx.findAndMerge(S.attr("with"), paramsMap)<br /> }<br /> }) match {<br /> case Full(x) => x<br /> case Empty => Comment("FIX"+ "ME: session or request are invalid")<br /> case Failure(msg, _, _) => Comment(msg)<br /> }<br />}</pre><br />Next in Boot.scala you will add the following code that will allow lift to use CachedSurround dispatcher when it encounters the "cachedSurround" lift tag(<lift:cachedSurround with="default" at="pageContent">...</lift:cachedSurround>) and use our CustomLiftSession that does the preprocessing and caching of merged templates (with cachedSurround and embed tags only) in production mode. <br /><br /><pre class="brush: scala; ruler: true;"> LiftRules.snippetDispatch.append(<br /> Map("cachedSurround" -> CachedSurround)<br /> )<br /><br /> LiftRules.sessionCreator = {<br /> case (httpSession, contextPath) => new CustomLiftSession(contextPath, httpSession.sessionId, Full(httpSession))<br /> }</pre><br />Now, many of this code can be avoided if you have a separate content handler dispatcher (this is added again in Boot.scala as LiftRules.dispatch.append(ContentHandler.dispatch)). Note that, this is the way if you want to do more before you process templates or load templates from some other place. I wont go into that detail, but if you do this then you will call the processTemplate on the session manually. So, this is what you can do in that case. Create the below code in net.liftweb.http package. When using below you do not need to extend LiftSession class nor create any new snippet dispatcher (whether you use surround or cachedSurround as in the comments below). <br /><br /><pre class="brush: scala; ruler: true; collapse: true;">object Expand{<br /> private lazy val logger = Logger(this.getClass)<br /><br /> def apply(template:NodeSeq) : NodeSeq = {<br /> template.flatMap{<br /> case Group(nodes) => apply(nodes)<br /> case CachedSnippetNode(element, kids, isLazy, attrs, snippetName)if(snippetName == "cachedSurround") => //This part is modified from Surround.scala lift code. Also you can use "surround" directly here if you dont want to create a new lift tag.<br /> (for (ctx <- S.session ?~ ("FIX ME: Invalid session")) yield {<br /> val mainParam = Map(attrs.asAttrMap("at") -> kids)<br /> ctx.findAndMerge(Full(attrs.asAttrMap("with")), mainParam)<br /> }) match {<br /> case Full(x) => apply(x)<br /> case Empty => Comment("FIX ME: session or request are invalid")<br /> case Failure(msg, _, _) => Comment(msg)<br /> }<br /> case CachedSnippetNode(element, kids, isLazy, attrs, snippetName)if(snippetName == "embed") =>//This part is modified from Embed.scala lift code<br /> (for {<br /> ctx <- S.session ?~ ("FIX ME: session is invalid")<br /> templateOpt <- ctx.findTemplate(attrs.asAttrMap("what")) ?~ ("FIX ME the embed tag has what="+attrs.asAttrMap("what")+" which is not found")<br /> } yield (attrs.get("what"),LiftSession.checkForContentId(templateOpt))) match {<br /> case Full((what,template)) => {<br /> val bindingMap : Map[String,NodeSeq] = Map(kids.flatMap({<br /> case p : scala.xml.PCData => None // Discard whitespace and other non-tag junk<br /> case t : scala.xml.Text => None // Discard whitespace and other non-tag junk<br /> case e : Elem if e.prefix == "lift" && e.label == "bind-at" => {<br /> e.attribute("name") match {<br /> /* DCB: I was getting a type error if I just tried to use e.child<br /> * here. I didn't feel like digging to find out why Seq[Node]<br /> * wouldn't convert to NodeSeq, so I just do it with fromSeq. */<br /> case Some(name) => Some(name.text -> NodeSeq.fromSeq(e.child))<br /> case None => logger.warn("Found <lift:bind-at> tag without name while embedding \"%s\"".format(attrs.asAttrMap("what"))); None<br /> }<br /> }<br /> case _ => None<br /> }): _*)<br /><br /> BindHelpers.bind(bindingMap, template)<br /> }<br /> case Failure(msg, _, _) => throw new SnippetExecutionException(msg)<br /><br /> case _ => throw new SnippetExecutionException("session is invalid")<br /> }<br /><br /> case v: Elem =>Elem(v.prefix, v.label, v.attributes,<br /> v.scope, apply(v.child): _*)<br /><br /> case pcd: scala.xml.PCData => pcd<br /> case text: Text => text<br /> case unparsed: Unparsed => unparsed<br /><br /> case a: Atom[Any] if (a.getClass == classOf[Atom[Any]]) => new Text(a.data.toString)<br /><br /> case v => v<br /> }<br /> }<br />}</pre><br />And in your content handler dispatcher, you will use it like this. <br /><br /><pre class="brush: scala; ruler: true;">val pathParams = S.request.get.path.wholePath<br />Templates(pathParams) match {<br /> case Full(template) => {<br /> val path = S.request.get.path.wholePath.mkString("/")<br /> val preXML: NodeSeq = if(ProcessedTemplateCache.has(path))ProcessedTemplateCache.get(path).get<br /> else ProcessedTemplateCache.set(path, Expand(template))<br /> S.session.get.processTemplate(Full(preXML), S.request.get, S.request.get.path, 200)<br /> }<br /> case _@Empty => logger.error("Parsing empty content while loading %s template.".format(filePath))<br /> Empty<br /> case error: Failure => logger.error(error)<br /> error<br />}</pre><br />This will improve the performance significantly on long run in production mode as you already have cached templates that are expanded and merged from cachedSurround and embed tags. Note that this improvement is conditional upon the number of templates you use, and the amount of cachedSurround and embed tag in work. Just to make it simple, I am providing the package and the imports below. <br /><br /><pre class="brush: scala; ruler: true; collapse: true;">package net.liftweb<br />package http<br /><br />import xml._<br /><br />import net.liftweb._<br />import net.liftweb.common._<br />import Box._<br />import util._<br />import Helpers._<br />import builtin.snippet._<br />import provider._<br />import xml.Group<br />import scala.Some<br />import xml.Text</pre> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-39679243358286774712012-06-28T19:42:00.001-07:002012-06-29T18:46:59.353-07:00Secure Remote Password Protocol in Scala<p>I was very interested in authentication and bumped against SRP. SRP is a protocol that helps in achieving authentication and is defined at <a href="http://srp.stanford.edu/">http://srp.stanford.edu/</a>. Since, I did not see any implementation of this in my current fav programming language- Scala, I decided to write a small scala based SRP v6a implementation. The code is hosted on Github at <a href="https://github.com/shreyaspurohit/SRPScala">https://github.com/shreyaspurohit/SRPScala</a> and is MIT licensed opensource. Feel free to contribute. I have provided an example implementation using Play 2.0 web framework. This is hosted at <a href="http://srp.bitourea.com/">http://srp.bitourea.com/</a>. The client is authenticated by server, then client authenticates that its speaking to the right server and finally continues to get the secret page over AES 128bit PBE encrypted channel. Since, just scala server side code is not sufficient- there is also srp.js that tags along for the client side javascript. The code scala and JS is small and can be easily understood. Just implement SRPServer trait and you are good to go. The example play 2.0 app provides a sample implementation- ExampleSRPServer.scala. This uses file for storage, you can use DB or any other destination for storage. The usage of javascript is provided as example in login.scala.html. It shows a very cleanly the data that is exchanged between client and server. This is how the example app works. These are just important code snippets. Look in Github for the complete code.</p> <pre class="brush: js; ruler: true;">var aVal = a();<br />var Aval = A(aVal);<br />var AvalHex = Aval.toHexString();<br />var username = $('#txtUsername').val();<br />var password = $('#txtPassword').val();</pre><br />Client gets a, Aval, username, password for the current session.<br /><pre class="brush: js; ruler: true;">$.ajax({<br /> url: '/login',<br /> type: 'POST',<br /> data: {"username" : username, "A" : AvalHex}<br /> })</pre><br />Client posts username and A to the server.<br /><pre class="brush: scala; ruler: true;">val Aval = params.get("A")<br />val username = params.get("username")</pre><br />Server gets the A and username from request.<br /><pre class="brush: scala; ruler: true;">val result = ExampleSRPServer.getSessionWithClientParameters(username(0), Aval(0))<br /> if(result.isDefined){<br /> val (sessionId, hSessionId, s, bvalStr) = result.get<br /> ExampleSRPServer.saveSession(username(0), sessionId, hSessionId)<br /> println(">>> session id: " + sessionId)<br /> Ok(s + "," + bvalStr)//Return the salt s and calculated value B<br /> }else{<br /> Ok("Error: Authentication Failure")<br /> }</pre><br />Server calculates the expected session id. There are some more authentication is real code. If the results are defined then save the current session, return s and bvalStr. Refer to SRP design to understand these parameters.<br /><pre class="brush: js; ruler: true;">var exch1 = data.split(",")<br />var Sval = currentSession(exch1[0], exch1[1], aVal, Aval, password)<br />sessionId=Sval;<br />setCookie("username", username);<br />validateServer();</pre><br />From the data received from server, client gets the current session. At this point, server has authenticated the client. Now, we need to make sure we are speaking to the right server.<br /><pre class="brush: js; ruler: true;">function validateServer(){<br /> var username = getCookie("username");<br /> Khex = K(sessionId)<br /> var Mval = M(Khex)<br /> var verifier = serverVerifier(Mval, Khex) <br /><br />$.ajax({<br /> url: '/validateServer',<br /> type: 'GET',<br /> data: {"username" : username, "K" : Mval}<br />})</pre><br />Client set the Khex, and the Mval for the current session. It posts username and Mval to server. The response from server must match the serverVerifier generated.<br /><pre class="brush: scala; ruler: true;">def validateServer(username:String, M:String) = Action(implicit request =>{<br /> import srp._<br /> val (sessionId, hSessionId) = ExampleSRPServer.getSessionWithHash(username)<br /> if(BigInt(ExampleSRPServer.M(hSessionId), 16).equals(BigInt(M,16))){<br /> Ok(ExampleSRPServer.verifier(hSessionId,M))<br /> }else{<br /> Ok("Error: Authentication Failure. M mismatch.")<br /> }<br /> })</pre><br />The server returns the verifier. The server and client have authenticated to each other. K has never been sent over the wire, so we use K as secret key for this session to encrypt data exchange between server and client. This is shown in method- getTheSecretPage() in login.scala.html. On the server side check Auth.scala for AES magic. The enc method on the server side does the job in scala.<br /><pre class="brush: scala; ruler: true;">def enc(username:String, sessionId:String, page:String) = <br /> EncryptedAuthenticatedPage(username, sessionId, page){decrypted =><br /> decrypted._3 match {<br /> case "secret" => Ok(decrypted._5(views.html.secret().body))<br /> case _ => NotFound(<h1>Page not found</h1>)<br /> } <br /> }</pre> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-76755743228386661692012-03-31T21:18:00.001-07:002012-03-31T21:25:43.640-07:00Gradle wrapper behind a proxyI was trying to setup gradle wrapper to work with Teamcity behind a proxy but only to realize that gradle wrapper was not using the java proxy options I was providing in command line. I decided to tweak the gradlew.sh and gradlew.bat a little to make it use the proxy so that I can provide proxy options in teamcity console when I configure the build step using gradlew. I don’t have access to build agents to configure gradle with properties file. Insert the below code in batch and shell script at a place between the first line and last line (Just before the invocation of GradleWrapperMain). If you put below pieces of code at wrong place then this will not work. <pre class="brush: bash; ruler: true; collapse: false;">set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar<br />.<br />.<br />.<br />@rem Add default JVM options by parsing command line. Anything starting with -D is option to java.<br />:SYSTEM_PROPS_EXTRACT<br />set _OPTION=%1<br />set prefix1=%_OPTION:~0,2%<br />if "%prefix1%" == "-D" ( <br /> SET DEFAULT_JVM_OPTS=%DEFAULT_JVM_OPTS% %_OPTION%=%2% <br />)<br />shift<br />if not "%~1"=="" goto SYSTEM_PROPS_EXTRACT<br /><br />echo Default JVM Options: %DEFAULT_JVM_OPTS%<br />.<br />.<br />.<br />.<br />@rem Execute Gradle<br />"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% <br />"-Dorg.gradle.appname=%APP_BASE_NAME%" <br />-classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%</pre><br />Shell Script changes <br /><br /><pre class="brush: bash; ruler: true; collapse: false;">.<br />.<br />.<br />.<br />#Add default JVM options by parsing command line arguments. <br />#Anything starting with -D is passed to JVM also.<br />for var in "$@"<br />do <br /> prefix=${var:0:2}<br /> if [[ $prefix == "-D" ]] ; then<br /> DEFAULT_JVM_OPTS=$DEFAULT_JVM_OPTS" "$var<br /> fi<br />done<br /><br /># Split up the JVM_OPTS And GRADLE_OPTS values into an array, <br /># following the shell quoting and substitution rules<br />function splitJvmOpts() {<br /> JVM_OPTS=("$@")<br />}<br />eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS<br />JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"<br /><br />echo "JVM_OPTS :""${JVM_OPTS[@]}"<br />.<br />.<br />.<br />exec "$JAVACMD" "${JVM_OPTS[@]}" <br />-classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"</pre><br />And finally the proxy option to use gradlew build -Dhttp.proxyHost=<a href="http://www-ad-proxy.sabre.com/">www-xxx.com</a> -Dhttp.proxyPort=80 -Dhttp.proxyUser=user1 -Dhttp.proxyPassword=user1 -Dhttp.auth.ntlm.domain=COMMON<br /><br /><br /> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-41890863567740669232012-01-04T18:54:00.001-08:002012-01-04T18:54:42.796-08:00Run Jetty in eclipse with scalatra<p>Though this is very simple and straight forward, there is no direct answer to this question on internet. You can run any webapp in eclipse using this approach, not necessarily with scalatra. The code is available for Lift web framework and works well with scalatra too.</p> <pre class="brush: scala; ruler: true; collapse: true;">import org.eclipse.jetty.server.Server<br />import org.eclipse.jetty.webapp.WebAppContext<br /><br />object RunWebApp {<br /> def main(args: Array[String]) {<br /> val server = new Server(9080)<br /> val context: WebAppContext = new WebAppContext();<br /> context.setServer(server)<br /> context.setContextPath("/");<br /> context.setWar("src/main/webapp")<br /> server.setHandler(context);<br /><br /> try {<br /> println(">>> STARTING EMBEDDED JETTY SERVER, PRESS ANY KEY TO STOP")<br /> server.start()<br /> server.join()<br /> } catch {<br /> case ex: Exception => {<br /> ex.printStackTrace()<br /> System.exit(1)<br /> }<br /> }<br /> }<br />}</pre> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-37247377054909402011-11-02T16:43:00.001-07:002011-11-02T16:47:31.425-07:00Atmosphere with jquery tutorial to stream data to web browser<blockquote> <p>When I started to learn Atmosphere to do async server push to client I was overwhelmed with the available tutorials on the net! The blog at <a href="http://jfarcand.wordpress.com/2010/06/15/using-atmospheres-jquery-plug-in-to-build-applicationsupporting-both-websocket-and-comet/" target="_blank">6312</a> is the best but it had too much details. Not much patience to read, I just needed a really quick start and not a really long page with lot of details. So, Here is a short tutorial to the people of my kinds who just want to learn and implement server push fast and dirty. For more details and explanation see the blog I mentioned before.</p> <p>I am using atmosphere 0.7.2, tomcat 6, and JDK 6.</p> <p><strong>Step 1:</strong> Download the sample jquery-pubsub-0.7.2.war from <a href="https://oss.sonatype.org/content/repositories/releases/org/atmosphere/samples/atmosphere-pubsub/0.7.2/" target="_blank">here</a> . Extract the war to a known directory. Copy the jars from the WEB-INF/lib to your webapp lib which needs the atmosphere support. Make sure that your application lib contains only one version of the jar. You do not want to find yourself troubleshooting problems due to multiple version of same jar in the classpath of your webcontainer.</p> <p><strong>Step 2:</strong> Create a folder META-INF in the same level as WEB-INF in your application war. Create context.xml from the content shown below and put the same file in both META-INF and WEB-INF. </p> <pre class="brush: xml; ruler: true; collapse: false;"><?xml version="1.0" encoding="UTF-8"?><br /><Context><br /> <Loader delegate="true"/><br /></Context></pre><br /> <p><strong>Step 3:</strong> Put the below snippet in web.xml between web-app xml tag. Make sure to note the “/account/number” and “/atm/*” path provided. We will use this path “/atm/account/number” to subscribe from the server as shown later.</p><br /> <pre class="brush: xml; ruler: true; collapse: false;"><servlet><br /> <servlet-name>AtmosphereServlet</servlet-name><br /> <description>AtmosphereServlet</description><br /> <servlet-class>org.atmosphere.cpr.AtmosphereServlet</servlet-class><br /> <init-param><br /> <param-name>grizzly.application.path</param-name><br /> <param-value>/account/number</param-value><br /> </init-param><br /> <init-param><br /> <param-name>org.atmosphere.useWebSocket</param-name><br /> <param-value>true</param-value><br /> </init-param><br /> <init-param><br /> <param-name>org.atmosphere.useNative</param-name><br /> <param-value>true</param-value><br /> </init-param><br /> <init-param><br /> <param-name>org.atmosphere.cpr.broadcastFilterClasses</param-name><br /> <param-value>org.atmosphere.client.FormParamFilter,org.atmosphere.client.JavascriptClientFilter<br /> </param-value><br /> </init-param><br /> <load-on-startup>0</load-on-startup><br /></servlet></pre><br /> <pre class="brush: xml; ruler: true; collapse: false;"><servlet-mapping><br /> <servlet-name>AtmosphereServlet</servlet-name><br /> <url-pattern>/atm/*</url-pattern><br /></servlet-mapping></pre><br /> <p><strong>Step 4:</strong> I would assume you know jquery. Create atm.js with the below code and add it to your collection of javascripts in war.</p><br /> <pre class="brush: js; ruler: true; collapse: true;">var topicSubs = 'all'<br />var urlLocation = 'http://localhost:8080/mywebapp/atm/account/';<br /><br />var onData = function(data){<br /> $('body').append("Message Received: " + data);<br />}<br />var appSubscriber = function() {<br /> var callbackAdded = false;<br /><br /> function subscribe() {<br /> function callback(response) { <br /> if (response.transport != 'polling' && response.state != 'connected' && response.state != 'closed') { <br /> if (response.status == 200) {<br /> var data = response.responseBody;<br /> if (data.length > 0 && data.search('-->')==-1) {<br /> onData(data);<br /> }<br /> }<br /> }<br /> }<br /><br /> <br /> $.atmosphere.subscribe(urlLocation + topicSubs, !callbackAdded ? callback : null,$.atmosphere.request = { transport: 'websocket' });<br /> callbackAdded = true;<br /> }<br /> <br /> subscribe();<br />}</pre><br /> <p><strong>Step 5:</strong> In your html page subscribe using the below shown code. Make sure to include the above script atm.js in the page. Here, I am overriding the variables declared in the atm.js above with required values. See the path in urlLocation, http”//localhost:8080/mywebapp” is your application URL, “/atm” is for atmosphere servlet URL pattern, “/account” is for jersey as you will see in server side code in sext step, and “/8832221” which is added to the URL by atm.js code is used by the server code to push data for that account number.</p<br /> <pre class="brush: js; ruler: true; collapse: true;"><script type="text/javascript"><br /> $(document).ready(function(){<br /> topicSubs = '8832221';<br /> urlLocation = 'http://localhost:8080/mywebapp/atm/account/';<br /> onData = function(data){<br /> $('#content').append("Message Received: " + data + "</br>");<br /> };<br /> appSubscriber();<br /> });<br /><br /></script></pre><br /> <p><strong>Step 6:</strong> Now that we have the html UI ready, we can write the server side code. Note that the html page will get updates only those that are pushed using the below push method after the html page has loaded and subscribed using the JS above. If you need old updates, then you have to cache when the subscriber is not available for a account and if you need to make sure not some random person can access the data from others account number, then an auth token can be embedded in the subscription string with account number that can be validated. You can code these requirements in the EventsLogger class that has the callback hooks from the atmosphere when different events occur. Some of you may not get updates if you are using different applications talking to each other due to cross domain issue which is solved by adding a http header in the below shown code. If you look at the path annotation it maps the “/account/{topic}”, where the topic is the account number from the html page that is sent to subscribe. Push method also prints all the broadcasters available which are equal to the subscribers from the html and have the id as the name.</p><br /> <pre class="brush: java; ruler: true; collapse: true;">import org.atmosphere.cpr.*;<br />import javax.servlet.http.HttpServletRequest;<br />import javax.servlet.http.HttpServletResponse;<br />import java.io.IOException;<br />import java.util.*;<br />import com.sun.jersey.spi.resource.Singleton;<br />import javax.ws.rs.*;<br />import javax.ws.rs.core.Context;<br /><br />import org.atmosphere.annotation.Broadcast;<br />import org.atmosphere.annotation.Resume;<br />import org.atmosphere.annotation.Suspend;<br />import org.atmosphere.cpr.AtmosphereHandler;<br />import org.atmosphere.jersey.Broadcastable;<br />import org.atmosphere.jersey.JerseyBroadcaster;<br />import org.atmosphere.jersey.SuspendResponse;<br /><br /><br />@Path("/account/{topic}")<br />@Produces("text/html;charset=ISO-8859-1")<br />public class OurAtmosphereHandler{<br /> private @PathParam("topic")<br /> Broadcaster topic;<br /><br /> @GET<br /> public SuspendResponse<String> subscribe(@Context HttpServletResponse httpResponse) {<br /> httpResponse.addHeader("Access-Control-Allow-Origin","*");<br /> return new SuspendResponse.SuspendResponseBuilder<String>()<br /> .broadcaster(topic)<br /> .outputComments(true)<br /> .addListener(new EventsLogger())<br /> .build();<br /><br /> }<br /><br /> public static void push(String message, String topic){<br /><br /> Collection<Broadcaster> broadcasters = BroadcasterFactory.getDefault().lookupAll();<br /><br /> for(Broadcaster b : broadcasters){<br /> System.out.println(b.toString());<br /> }<br /><br /> System.out.println("Request to push- Message: " + message + ", Topic: " + topic);<br /> Broadcaster b = null;<br /> if(null != (b = BroadcasterFactory.getDefault().lookup(JerseyBroadcaster.class,topic))){<br /> System.out.println("Request to push- Message: " + message + ", Topic: " + topic);<br /> b.broadcast(message + "\n");<br /> }<br /> }<br /><br /> private class EventsLogger implements AtmosphereResourceEventListener {<br /><br /> @Override<br /> public void onSuspend(AtmosphereResourceEvent<HttpServletRequest, HttpServletResponse> event) {<br /> event.getResource().getResponse().addHeader("Access-Control-Allow-Origin","*");<br /> System.out.println("onSuspend(): " + event.getResource().getRequest().getRemoteAddr() + " : " + event.getResource().getRequest().getRemoteHost()); <br /> }<br /><br /> @Override<br /> public void onResume(AtmosphereResourceEvent<HttpServletRequest, HttpServletResponse> event) {<br /> event.getResource().getResponse().addHeader("Access-Control-Allow-Origin","*");<br /> System.out.println("onResume(): " + event.getResource().getRequest().getRemoteAddr() + " : " + event.getResource().getRequest().getRemoteHost());<br /> }<br /><br /> @Override<br /> public void onDisconnect(AtmosphereResourceEvent<HttpServletRequest, HttpServletResponse> event) { <br /> System.out.println("onDisconnect(): " + event.getResource().getRequest().getRemoteAddr() + " : " + event.getResource().getRequest().getRemoteHost());<br /> }<br /><br /> @Override<br /> public void onBroadcast(AtmosphereResourceEvent<HttpServletRequest, HttpServletResponse> event) {<br /> event.getResource().getResponse().addHeader("Access-Control-Allow-Origin","*");<br /> System.out.println("onBroadcast(): " + event.getMessage());<br /> }<br /><br /> @Override<br /> public void onThrowable(AtmosphereResourceEvent<HttpServletRequest, HttpServletResponse> event) { <br /> System.out.println("onThrowable(): " + event);<br /> }<br /> }<br /><br />}</pre><br />So, have fun using streaming technology using atmosphere. I would suggest using chrome as it supports websockets and is most reliable. You can use polling if working with other browsers. Just change the transport in the atm.js with “polling”. Atmosphere is an awesome technology and I would suggest to visit the blog 6312 that I mentioned in the beginning for more details and many different options and customization.</blockquote> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-23977862845792545212011-09-21T18:28:00.001-07:002011-11-02T16:56:04.189-07:00Solution for: Spring JMS + ActiveMQ + Tomcat = Tomcat does not shutdown<p>This is one of the most frustrating problems. When you decide to use Tomcat with Spring JMS to connect and message with ActiveMQ, everything works fine till you try to shutdown Tomcat.  It keeps throwing exception and looks like the DefaultMessageListnerContainer thread lives on. As seen from the logs on tomcat console, the problem occurs because the tomcat shutsdown before the JMS Spring container “DefaultMessageListenerContainer” and you will see Nullpointer, Classnotfound exceptions, as tomcat container is down and the classloader doesn’t exist to give the necessary classes from the previously loaded jars. And I could not find a single solution on google. After, much googling I found a solution for a similar problem when using Quartz, tomcat and spring. So, this solution was derived from that one [Sorry, I don’t have that link anymore].</p> <p>Create a ServletContextListener, and in contextDestroyed method get the jms container bean to manually call shutdown. The listener contextInitialized and contextDestroyed are called at start and end of Servlet lifecycle. The below code assumes that you have a class called SpringApplicationContextInstance that gives you the spring context using which you can access your beans defined in spring context.</p> <pre class="brush: java; ruler: true; collapse: true;">...<br />import javax.servlet.ServletContextEvent;<br />import javax.servlet.ServletContextListener;<br /><br />/**<br /> * Created by Shreyas Purohit<br /> *<br /> */<br />public class JMSContainerShutDownHook implements ServletContextListener {<br /> @Override<br /> public void contextInitialized(ServletContextEvent servletContextEvent) {<br /> LogManager.log(Level.INFO, "JMSContainerShutDownHook: Initialized called");<br /> }<br /><br /> @Override<br /> public void contextDestroyed(ServletContextEvent servletContextEvent) {<br /> LogManager.log(Level.INFO, "JMSContainerShutDownHook: Destroyed called");<br /> try {<br /> LogManager.log(Level.INFO, "JMSContainerShutDownHook: Fetching JMS Container Bean from Application Context");<br /> DefaultMessageListenerContainer container = SpringApplicationContextInstance.getInstance().getBean("jmsContainer");<br /> LogManager.log(Level.INFO, "JMSContainerShutDownHook: Calling shutdown on DefaultMessageListenerContainer");<br /> container.shutdown();<br /> Thread.sleep(3000); //Wait for the container to shutdown<br /> } catch (Exception e) {<br /> e.printStackTrace();<br /> LogManager.log(Level.ERROR, e);<br /> }<br /> LogManager.log(Level.INFO, "JMSContainerShutDownHook: Exiting");<br /> }<br />}</pre> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-75070396083994132602011-09-14T12:50:00.001-07:002011-09-14T12:52:17.661-07:00ActiveMQ 5.5.0 performance test plugin<p>It not very simple to apply the perf test instructions given at <a href="http://activemq.apache.org/activemq-performance-module-users-manual.html">http://activemq.apache.org/activemq-performance-module-users-manual.html</a> I encountered many problems, and here is a way to make it run.</p> <p><strong>Required: </strong>Maven 2, SVN client like tortoise SVN</p> <p><strong>Steps</strong></p> <p><strong>1. </strong>Checkout the source code for Active MQ 5.5.0 from SVN <a title="http://svn.apache.org/repos/asf/activemq/tags/activemq-5.5.0" href="http://svn.apache.org/repos/asf/activemq/tags/activemq-5.5.0">http://svn.apache.org/repos/asf/activemq/tags/activemq-5.5.0</a> to directory AMQ</p> <p><strong>2.</strong> Checkout perf test code from SVN <a title="http://svn.apache.org/repos/asf/activemq/sandbox/activemq-perftest/" href="http://svn.apache.org/repos/asf/activemq/sandbox/activemq-perftest/">http://svn.apache.org/repos/asf/activemq/sandbox/activemq-perftest/</a> to directory AMQPerf</p> <p><strong>3.</strong> cd AMQ/activemq-tooling/maven-activemq-perf-plugin and edit pom.xml. Remove line <scope>test</scope> from org.slf4j dependency.</p> <p><strong>4. </strong>cd AMQPerf and edit pom.xml. Change <version> in <parent> to 5.5.0</p> <p><strong>5. </strong>cd AMQ/activemq-tooling and run “mvn clean install”</p> <p><strong>6. </strong>cd AMQPerf and run “mvn clean install”</p> <p><strong>7. </strong>Run Commands as provided in <a title="http://activemq.apache.org/activemq-performance-module-users-manual.html" href="http://activemq.apache.org/activemq-performance-module-users-manual.html">http://activemq.apache.org/activemq-performance-module-users-manual.html</a> from the AMQPerf directory in separate command prompts/Consoles, that is- </p> <pre>Console1> mvn activemq-perf:consumer<br />Console2> mvn activemq-perf:producer</pre><br />This works for me perfectly. SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com2tag:blogger.com,1999:blog-36956413.post-49467560619373796972011-09-12T20:26:00.000-07:002011-09-12T20:29:40.484-07:00Ruby On Rails mysql2 error<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">I was having trouble installing mysql2 gem on my Win7 64 bit. I have copied these instructions from stackoverflow. And these really work.</span><br />
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><br /></span><br />
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><b>For Win 7:</b></span><br />
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><br /></span><br />
<span class="Apple-style-span" style="background-color: white; font-family: Arial, Helvetica, sans-serif; line-height: 12px;"></span><br />
<div class="post-text" style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; margin-bottom: 5px; margin-left: 0px; margin-right: 5px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline; width: 660px;">
<ol style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; line-height: 18px; list-style-image: initial; list-style-position: initial; list-style-type: decimal; margin-bottom: 1em; margin-left: 30px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline;">
<li style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline; word-wrap: break-word;"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">Download a zip file with mysql server 5.1 NOT the msi one. Make sure it's 32-bit NOT 64-bit. (<a href="http://dev.mysql.com/downloads/mysql/5.1.html" rel="nofollow" style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; cursor: pointer; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-decoration: none; vertical-align: baseline;">From here</a>) </span></li>
<li style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline; word-wrap: break-word;"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">Since there is no installer file with this, create a folder c:\mysql-gem-install - you can remove it once you finish.</span></li>
<li style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline; word-wrap: break-word;"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">Extract all the files from the zip file into the folder you just created.</span></li>
<li style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline; word-wrap: break-word;"><div style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; clear: both; margin-bottom: 1em; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline; word-wrap: break-word;">
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">now run this command</span></div>
<div style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; clear: both; margin-bottom: 1em; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline; word-wrap: break-word;">
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">gem install mysql2 -- '--with-mysql-lib="c:\mysql-gem-install\lib\opt" --with-mysql-include="c:\mysql-gem-install\include"'</span></div>
</li>
</ol>
<div style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; clear: both; line-height: 18px; margin-bottom: 1em; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline; word-wrap: break-word;">
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">I just installed mysql2 gem v. 0.3.7</span></div>
<div style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; clear: both; line-height: 18px; margin-bottom: 1em; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline; word-wrap: break-word;">
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span style="background-attachment: initial; background-clip: initial; background-color: transparent; background-image: initial; background-origin: initial; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; vertical-align: baseline;">EDIT:</span> One more thing: make sure you run the command in Command Prompt directly. As in not PowerShell or Consol2 - for some reason if you try that it will give you and error " invalid option" - has to do with the way -- is parsed.</span></div>
<div style="line-height: 18px;">
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><i>--Answered by <span class="Apple-style-span" style="background-color: white; line-height: 17px; white-space: nowrap;">Nick Gorbikoff</span></i></span></div>
<div style="line-height: 18px;">
<span class="Apple-style-span" style="background-color: white; font-family: Arial, Helvetica, sans-serif; line-height: 17px; white-space: nowrap;"><br /></span></div>
<div style="line-height: 18px;">
<span class="Apple-style-span" style="background-color: white; font-family: Arial, Helvetica, sans-serif; line-height: 17px; white-space: nowrap;"><b>For Linux:</b></span></div>
<div style="line-height: 18px;">
<span class="Apple-style-span" style="background-color: white; font-family: Arial, Helvetica, sans-serif; line-height: 17px; white-space: nowrap;"><br /></span></div>
<div>
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="background-color: white; line-height: 17px; white-space: nowrap;">sudo apt-get install libmysql-ruby libmysqlclient-de</span><span class="Apple-style-span" style="background-color: white; line-height: 17px; white-space: nowrap;">v</span></span></div>
<div>
<span class="Apple-style-span" style="background-color: white; font-family: Arial, Helvetica, sans-serif; line-height: 17px; white-space: nowrap;"><br /></span></div>
<div>
<span class="Apple-style-span" style="background-color: white; font-family: Arial, Helvetica, sans-serif; line-height: 17px; white-space: nowrap;"><i>--Answered by spacemonkey</i></span></div>
<div>
<span class="Apple-style-span" style="background-color: white; font-family: Arial, Helvetica, sans-serif; line-height: 17px; white-space: nowrap;"><br /></span></div>
<div>
<span class="Apple-style-span" style="background-color: white; font-family: Arial, Helvetica, sans-serif; line-height: 17px; white-space: nowrap;">I successfully installed mysql 5.5, and gem mysql2-0.3.7</span></div>
</div>
SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com1Grapevine, TX, USA32.9342919 -97.078065432.880984399999996 -97.1570294 32.9875994 -96.9991014tag:blogger.com,1999:blog-36956413.post-4827510480914484512011-08-31T14:39:00.000-07:002011-08-31T14:41:30.290-07:00Install Ruby gems when behind a proxyThere are many posts out there on the net instructing you to do certain thing when behind a proxy. Different things work for different people, but none of that worked for me. So, try this one if you are facing trouble with proxy too, and keep your fingers crossed! I am using Ruby 1.9.2, with gems version 1.7.2 on Windows 7. The below command allowed me to use proxy and download and install any gem.<br />
<br />
<br />
<div style="text-align: center;">
<blockquote style="color: #38761d;">
<b>gem install gemname -p http://username:password@proxyaddress:port --platform=ruby</b></blockquote>
</div>
<br />SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com03701 Grapevine Mills Pkwy, Grapevine, TX 76051, USA32.978716146003336 -97.03880310058593832.925430146003336 -97.117767100585937 33.032002146003336 -96.959839100585938tag:blogger.com,1999:blog-36956413.post-74371691322606992682011-08-25T05:11:00.001-07:002011-08-25T18:18:04.332-07:00Send SMS Using Ruby and Send Email using Ruby with Gmail SMTP Server<p>There was a HP Touchpad sale event where HP tablets where being sold out for $99 yesterday. Instead of manually monitoring the HP website, I decided to write a small Ruby script that will monitor and send SMS and Email to notify. I wanted to use Gmail SMTP server for mail, but that does work by default with Ruby SMTP support as it uses TLS. Finally, I found a ruby script on the internet which provides that support. I have copy pasted it from that website. There are many scripts out there on web, but none really work with gmail. Hence, it is important to spread this script. To send SMS I use the free <a title="http://www.textsurprise.com" href="http://www.textsurprise.com">http://www.textsurprise.com</a> service by making GET request using open-uri. The best thing about Ruby syntax is, its readable and need no explanation. </p> <pre class="brush: ruby; ruler: true; collapse: true;"># Requests the HP page for 16GB Tablet URL:http://www.shopping.hp.com/product/rts_tablet/rts_tablet/1/storefronts/FB355UA%2523ABA?aoid=35252<br /># If status "Coming soon" is not displayed then alert using SMS and email<br /># SMS: http://www.textsurprise.com<br /># Email: Uses gmail as sender smtp<br /><br />require 'open-uri'<br />require 'net/smtp'<br />require_relative 'smtp_tls'<br /><br />URL_16GB = 'http://www.shopping.hp.com/product/rts_tablet/rts_tablet/1/storefronts/FB355UA%2523ABA?aoid=35252'<br />URL_32GB = 'http://www.shopping.hp.com/webapp/product/rts_tablet/rts_tablet/1/storefronts/FB359UA%2523ABA?aoid=35252'<br />$carriers = {:auto => 0, :att => 3, :tmobile => 11, :metropcs => 12, :verizon => 16, :virgin => 21, :sprint => 9}<br />$numbers = {"XXXXXXXXXX" => :att}<br />$emails = ["xxxxxx@gmail.com"]<br />$notInterestedStatuses = ["Coming soon", "not found", "outofstock"]<br />$urls = {[URL_16GB,"HP_Website_16GB",$notInterestedStatuses] => false, [URL_32GB,"HP_Website_32GB",$notInterestedStatuses] => false}<br />$running = true;<br />$sleepTimeInSec = 240<br /><br />def send_email(to,subject,message)<br /> username = 'xxxxxxxxx'<br /> password = 'yyyyyyyyyy'<br /> emailMessage = <<MESSAGE_END<br />From: Shreyas Purohit <xxxxxxxxxx@gmail.com><br />To: #{to} <#{to}><br />Subject: #{subject}<br /><br />#{message}<br />MESSAGE_END<br /> <br /> Net::SMTP.start( 'smtp.gmail.com' , <br /> 587, <br /> 'gmail.com', <br /> username, <br /> password, <br /> 'plain' ){ |smtp|<br /> smtp.send_message( emailMessage, <br /> "xxxxxxxxxx@gmail.com", <br /> to)<br /> }<br />end<br /><br />def readWebsite(url) <br /> uriContent = ""<br /> open(url){|f| uriContent = f.read }<br /> return uriContent <br />end<br /><br />def notifyObservers(customMessage, url)<br /> # Send SMS using URL <br /> $numbers.each do |key, value| <br /> open("http://www.textsurprise.com/a.php?api=true&from=purohit@hotmail.com&phone=#{key.to_s}&amount=1&message=Check_HP_website_,_status_changed_#{customMessage}&carrier=#{$carriers[value].to_s}&data=0"){|f| <br /> p f.read} <br /> end<br /> <br /> send_email($emails,"Alert- Website Update","Alert! Website Status Changed. Please Check Online (#{customMessage}) (#{url})")<br /> <br />end<br /><br />def checkStatusOnWebsiteString(uriContent, statusNotOfInterestOnSite)<br /> statusNotOfInterestOnSite.each do |status|<br /> if (uriContent.include? status)<br /> return false<br /> end<br /> end<br /> return true<br />end<br /><br />def logToFile(fileName, mode, content)<br /> File.open(fileName, mode) {|f| f.write(content) }<br />end<br /><br />def process(urls) <br /> checkedStatusOnce = false<br /> statusChangedKeys = []<br /> <br /> urls.each do |key, status|<br /> url = key[0];<br /> customMessage = key[1];<br /> if !status<br /> checkedStatusOnce = true<br /> p "#{Time.now} : Trying HP Site #{customMessage} Now..."<br /> uriContent = readWebsite(url);<br /> statusChanged = checkStatusOnWebsiteString(uriContent,key[2])<br /> logToFile("alter_#{customMessage}.log", "w+", "URI DATA AT " + Time.now.to_s + " for url #{url} \n" + uriContent)<br /> if statusChanged<br /> p "Status changed..(#{customMessage}) Notifying Observers.."<br /> notifyObservers(customMessage, url)<br /> statusChangedKeys << key <br /> else<br /> p "Status Not Changed... (#{customMessage})"<br /> end <br /> end <br /> end<br /> <br /> statusChangedKeys.each do |key|<br /> urls[key] = true<br /> end<br /> <br /> if !checkedStatusOnce<br /> $running = false<br /> end<br />end<br /><br />while $running do<br /> p "#{Time.now} : Sleeping #{$sleepTimeInSec} sec(#{$sleepTimeInSec/60.0} min)..."<br /> sleep($sleepTimeInSec) <br /> process($urls)<br />end</pre><br />Also, The script smtp_tls.rb is below that is required for the above script to run.<br /><pre class="brush: ruby; ruler: true; collapse: true;">require "openssl"<br />require "net/smtp"<br /><br />Net::SMTP.class_eval do<br /> private<br /> def do_start(helodomain, user, secret, authtype)<br /> raise IOError, 'SMTP session already started' if @started<br /> check_auth_args user, secret, authtype if user or secret<br /><br /> sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }<br /> @socket = Net::InternetMessageIO.new(sock)<br /> @socket.read_timeout = 60 #@read_timeout<br /> @socket.debug_output = STDERR #@debug_output<br /><br /> check_response(critical { recv_response() })<br /> do_helo(helodomain)<br /><br /> raise 'openssl library not installed' unless defined?(OpenSSL)<br /> starttls<br /> ssl = OpenSSL::SSL::SSLSocket.new(sock)<br /> ssl.sync_close = true<br /> ssl.connect<br /> @socket = Net::InternetMessageIO.new(ssl)<br /> @socket.read_timeout = 60 #@read_timeout<br /> @socket.debug_output = STDERR #@debug_output<br /> do_helo(helodomain)<br /><br /> authenticate user, secret, authtype if user<br /> @started = true<br /> ensure<br /> unless @started<br /> # authentication failed, cancel connection.<br /> @socket.close if not @started and @socket and not @socket.closed?<br /> @socket = nil<br /> end<br /> end<br /><br /> def do_helo(helodomain)<br /> begin<br /> if @esmtp<br /> ehlo helodomain<br /> else<br /> helo helodomain<br /> end<br /> rescue Net::ProtocolError<br /> if @esmtp<br /> @esmtp = false<br /> @error_occured = false<br /> retry<br /> end<br /> raise<br /> end<br /> end<br /><br /> def starttls<br /> getok('STARTTLS')<br /> end<br />end</pre><br />So, that’s it! Have fun reusing code!! <br /><br /><br /> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com1tag:blogger.com,1999:blog-36956413.post-58564174727423603522011-07-26T14:33:00.000-07:002011-08-10T14:43:57.033-07:00Using JS caching on serverside data with jquery datatables plugin<p>First of all, I would like to say that I have been working on lots of stuff but have had no time to update this blog. Recently I was speaking with my friend who checks my blogs regularly, and was inspired to make some time and post stuff that is useful for many. So, here is one of the things that I worked on and pretty useful.</p> <p>Pagination is one of the most important concepts that must be implemented correctly for better performance of the database and that of your application. It is common sense to say that caching all the data on the server session is bad, or contacting database for every page is also bad; esp when working on tables with millions of records. The better alternative is to load and cache a good set of data on UI, and as and when needed update cache with more data. This removes caching done on server side, say in session, and negative performance impact that you get when you query database often while pagination. </p> <p>Many use jquery framework on javascript side. One of the plugins that I have worked with is datatables. Located at <a href="http://www.datatables.net/">http://www.datatables.net/</a> . There exist other frameworks like YUI for pagination and definitely other plugins for jquery framework for pagination. The datatables plugin works with server side data but does not provide good caching mechanism. Every new page is a request for getting data from the server to display that page. I wrote the below given javascript function that can be used with “fnServerData” option while initializing the datatables to provide caching.</p> <pre class="brush: js; ruler: true; collapse: true;">var cache = new Object();<br />var rowsToCache = 2000; //Minimum Cache size >(must) 2 * Maximum Display Length * Number of Navigation Button (Avoids false hits while traversing distant pages), Default Assumption: 2000 > 2 * 100 * 5<br />//Intelligient to fetch only unfetched data. Maintains cache window to left(rowsToCache/2) and right(rowsToCache/2) of current position.<br /><br />var fnJSManagedCacheServer = <br />function ( sSource, aoData, fnCallback ) {<br /> //Get the required variable values<br /> var _tableDispLength;<br /> var _indexDisplayStart;<br /> var _searchData;<br /> var sEcho;<br /> var _useCache = true;<br /> var _bIsSearch = false;<br /><br /> for(var i in aoData){<br /> var d = aoData[i];<br /> if(d.name == 'iDisplayLength') {<br /> _tableDispLength = d.value;<br /> }<br /> if(d.name == 'iDisplayStart') {<br /> _indexDisplayStart = d.value;<br /> }<br /> if(d.name == 'sSearch'){<br /> _searchData = d.value;<br /> }<br /> if(d.name == 'sEcho'){<br /> sEcho = d.value;<br /> }<br /> }<br /> //Process all conditions that may result in a true call and not access cache<br /> //Find if the call is result of only pagination and not because of search<br /> if(_searchData != undefined && _searchData != ""){<br /> _useCache = false;<br /> _bIsSearch = true;<br /> }<br /> //If cache doesnot exist<br /> if(cache['aaDataServer'] == undefined){<br /> _useCache = false;<br /> }<br /> //If _useCache is still true, try to get data from the cache, else allow call to server<br /> //by setting _useCache to false<br /> if(_useCache == true){<br /> //Predict False hit<br /> _useCache = false;<br /><br /> //Get data from cache and paint<br /> //Does requested page end is lesser than or equal to cache endpage<br /> if(_indexDisplayStart + _tableDispLength <= (cache.cached_indexDisplayStart + cache.realLength)<br /> || cache.realLength < cache.requestedLength){//Last page and no more data present at server<br /> //Does requested _indexDisplayStart is greater than or equal to cache _indexDisplayStart<br /> if(_indexDisplayStart >= (cache.cached_indexDisplayStart)){<br /> //Data must exist in cache<br /> var json = cache['jsonResult'];<br /> var aaData = cache['aaDataServer'].slice(Math.abs(cache.cached_indexDisplayStart - _indexDisplayStart), Math.abs(cache.cached_indexDisplayStart - _indexDisplayStart) + _tableDispLength < cache['aaDataServer'].length ? Math.abs(cache.cached_indexDisplayStart - _indexDisplayStart) + _tableDispLength : cache['aaDataServer'].length);<br /> json.aaData = aaData;<br /> json.sEcho = sEcho;<br /> fnCallback(json);<br /> //Cache hit<br /> _useCache = true;<br /> }<br /> }<br /> }<br /><br /> if(_useCache == false){<br /><br /> //Get rowsToCache/2 from existing cache and save<br /> var _prevCacheData = [];<br /> var _movingRight = true;<br /> if(cache.cached_indexDisplayStart != undefined){<br /> if(_indexDisplayStart > cache.cached_indexDisplayStart){<br /> _movingRight = true;<br /> }else{<br /> _movingRight = false;<br /> }<br /> if(_indexDisplayStart > cache.cached_indexDisplayStart<br /> && _indexDisplayStart == (cache.cached_indexDisplayStart + cache['aaDataServer'].length)){<br /> _prevCacheData = cache['aaDataServer'].slice(cache['aaDataServer'].length - rowsToCache/2);<br /> }else if(_indexDisplayStart <= cache.cached_indexDisplayStart<br /> && _indexDisplayStart == (cache.cached_indexDisplayStart - _tableDispLength)){<br /> _prevCacheData = cache['aaDataServer'].slice(0, cache['aaDataServer'].length == rowsToCache ? rowsToCache/2 : 0);<br /> }<br /> }<br /> //Invalidate and re-init cache<br /> cache = new Object();<br /> cache._tableDispLength = _tableDispLength;<br /> cache._indexDisplayStart = _indexDisplayStart;<br /> cache['aaDataServer'] = _prevCacheData;<br /> cache._movingRight = _movingRight;<br /><br /> if(_bIsSearch != true){<br /> //Modify settings of datatables to fetch rowsToCache records<br /> cache.requestedLength = 0;<br /> var _indexDisplayStartIsZero = false;<br /> for(var i in aoData){<br /> var d = aoData[i];<br /> if(d.name == 'iDisplayStart') {<br /> cache.requestedLength += d.value - (rowsToCache/2) >= 0 ? rowsToCache/2 : 0; //Order of these stmts important<br /> if(cache._movingRight == false){<br /> d.value = (d.value+_tableDispLength) - (rowsToCache/2) >= 0 ? (d.value+_tableDispLength) - (rowsToCache/2) : 0; //Shift _indexDisplayStart to left by half cache page size (adding _tableDispLength to get the _indexDisplayStart to original location before taking it forward, else we will miss a page)<br /> cache.cached_indexDisplayStart = d.value;<br /> }else{<br /> cache.cached_indexDisplayStart = d.value - _prevCacheData.length;<br /> }<br /> }<br /> }<br /> for(var i in aoData){<br /> var d = aoData[i];<br /> if(d.name == 'iDisplayLength') {<br /> d.value = cache.cached_indexDisplayStart != 0 ? rowsToCache/2 : rowsToCache/2;<br /> cache.requestedLength = d.value; //Adjust cache fetch requested<br /> }<br /> }<br /> }<br /> cache._bIsSearch = _bIsSearch;<br /> //Use ajax to Call<br /> $.ajax( {<br /> "dataType": 'json',<br /> "type": "POST",<br /> "url": sSource,<br /> "data": aoData,<br /> "success": function(json) {<br /> //Cache<br /> cache['jsonResult'] = json;<br /> if(cache._movingRight == true){<br /> cache['aaDataServer'] = cache['aaDataServer'].concat(json.aaData.slice(0));//append entire aaData to right of existing cached data<br /> }else{<br /> cache['aaDataServer'] = (json.aaData.slice(0)).concat(cache['aaDataServer']);//append entire aaData to left of existing cached data<br /> }<br /> cache.realLength = cache['aaDataServer'].length;<br /> if(cache._bIsSearch != true){<br /> //redraw a part of json, as requested between 0 and current pagesize(cache._tableDispLength)<br /> json.aaData = cache['aaDataServer'].slice(Math.abs(cache.cached_indexDisplayStart - cache._indexDisplayStart), cache.realLength < Math.abs(cache.cached_indexDisplayStart - cache._indexDisplayStart) + cache._tableDispLength ? cache.realLength : Math.abs(cache.cached_indexDisplayStart - cache._indexDisplayStart) + cache._tableDispLength);<br /> }<br /> fnCallback(json);<br /> }<br /> } );<br /> }<br /> }</pre><br />To use this, just include the javascript, override “rowsToCache” if default is not appropriate for you, and set “fnServerData” : fnJSManagedCacheServer.<br /><br /><p>Do let me know if you find any bugs! </br><br />PS: No caching when searching. </p> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com1tag:blogger.com,1999:blog-36956413.post-60953780584639475982010-10-22T15:22:00.001-07:002010-10-22T15:51:13.642-07:00javaFx custom component– Line segment with arrowhead<p>I have been working with javaFx for some time now for projects in School during my M.S tenure. I love the ease with which I can create new components. The only complain I have had till now is that it is not fast enough to handle really fast animations. It was very patchy when I worked with it in a project with multiple images/lines/rectangles on stage changing at rate of 200ms. It simply could not handle it. The Timeline class itself takes a moment in time hindering animations. Just a claim about Timeline with no proofs. Coming back to the problem, a line segment with arrowhead. A very trivial feature missing in javaFx and many people searching for a solution. Here is a custom component which does that exactly the same using CustomNode, Line and a Polygon.</p> <pre class="brush: javafx;">import javafx.scene.shape.Line;<br />import javafx.scene.CustomNode;<br />import javafx.scene.Node;<br />import javafx.scene.Group;<br />import javafx.scene.shape.Polygon;<br />import javafx.scene.paint.Paint;<br />import javafx.scene.transform.Transform;<br />import java.lang.Math;<br /><br />/**<br /> * @author Shreyas Purohit<br /> */<br /><br />public class ArrowHeadLine extends CustomNode{<br /> var group:Group;<br /> public var x:Number;<br /> public var y:Number;<br /><br /> public var startX:Number on replace{<br /> handleChange();<br /> };<br /> public var startY:Number on replace{<br /> handleChange();<br /> };<br /> public var endX:Number on replace{<br /> handleChange();<br /> };<br /> public var endY:Number on replace{<br /> handleChange();<br /> };<br /> public var fill:Paint;<br /> public var stroke:Paint;<br /><br /> var arrowhead:Polygon;<br /> var segment:Line;<br /><br /> override protected function create () : Node {<br /> group = Group{<br /> translateX: bind x;<br /> translateY: bind y;<br /> content:[<br /> segment = Line{<br /> startX: bind startX<br /> startY: bind startY<br /> endX: bind endX<br /> endY: bind endY<br /> stroke: bind stroke;<br /> },<br /> arrowhead = Polygon{<br /> points :[-6,-6,<br /> 0,0,<br /> -6,6];<br /> fill: bind fill;<br /> }<br /> ]<br /> }<br /><br /> }<br /><br /> function handleChange():Void{<br /> var angle:Number = Math.toDegrees(Math.atan2(endY-startY, endX-startX));<br /> arrowhead.translateX = endX;<br /> arrowhead.translateY = endY;<br /> arrowhead.transforms = [Transform.rotate(angle,0,0)]<br /> }<br /><br />}</pre> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-60971480479925029952010-10-18T21:34:00.001-07:002010-10-18T21:36:21.649-07:00Betwixt and List/ArrayList<p>I just can not imagine wasting 3-4 hours to just make Betwixt from Apache (<a title="http://commons.apache.org/betwixt/index.html" href="http://commons.apache.org/betwixt/index.html">http://commons.apache.org/betwixt/index.html</a>) work with List and ArrayList in my java beans. Betwixt simply does not work with List/ArrayList when converting from XML to java bean unless I stop using List/ArrayList completely and replace them with arrays or the solution that I used – create a .betwixt file to configure its use. If you adopt the first option then I found out that if I have 2 array types defined in my class then a weird ClassCastException to java.util.List was being encountered. I finally successfully used List/ArrayList in my class.</p> <p><strong><u>Steps</u></strong></p> <p>1. Define the class say, Info – java bean – to be serialized to XML and back using List/ArrayList as shown. Note the last two methods – addRouteInfo and addEntityInfo. These are the key methods to be added to your java bean.</p><pre class="brush: java;">package com.ssb.repo;<br /><br />import java.util.ArrayList;<br />import java.util.List;<br />import java.util.Map;<br />/**<br /> * @author Shreyas P<br /> *<br /> */<br />public class Info {<br /> private String siteId;<br /> private String applicationId;<br /> private List<RouteInfo> routeInfos = new ArrayList<RouteInfo>();<br /> private List<EntityInfo> entityInfos = new ArrayList<EntityInfo>();<br /> <br /> public Info(){<br /> <br /> }<br /> <br /> /**<br /> * @return the siteId<br /> */<br /> public String getSiteId() {<br /> return siteId;<br /> }<br /> /**<br /> * @param siteId the siteId to set<br /> */<br /> public void setSiteId(String siteId) {<br /> this.siteId = siteId;<br /> }<br /> /**<br /> * @return the applicationId<br /> */<br /> public String getApplicationId() {<br /> return applicationId;<br /> }<br /> /**<br /> * @param applicationId the applicationId to set<br /> */<br /> public void setApplicationId(String applicationId) {<br /> this.applicationId = applicationId;<br /> }<br /> /**<br /> * @return the entityInfos<br /> */<br /> public List<EntityInfo> getEntityInfos() {<br /> return entityInfos;<br /> }<br /> /**<br /> * @param entityInfos the entityInfos to set<br /> */<br /> public void setEntityInfos(List<EntityInfo> entityInfos) {<br /> this.entityInfos = entityInfos;<br /> }<br /> /**<br /> * @return the routeInfos<br /> */<br /> public List<RouteInfo> getRouteInfos() {<br /> return routeInfos;<br /> }<br /><br /> /**<br /> * @param routeInfos the routeInfos to set<br /> */<br /> public void setRouteInfos(List<RouteInfo> routes) {<br /> this.routeInfos = routes;<br /> }<br /> <br /> public void addRouteInfo(RouteInfo routeInfo) {<br /> routeInfos.add(routeInfo);<br /> }<br /> <br /> public void addEntityInfo(EntityInfo entityInfo) {<br /> entityInfos.add(entityInfo);<br /> }<br />}</pre><br /><p>2. Create .betwixt file naming <classname>.betwixt. In our case, Info.betwixt. Betwixt automatically uses this file and need not be supplied as an argument to any function. Just place the file along with your class file. Note the ‘updates’ attribute, they link to the method to be used to add to the list/arraylist. Do not forget to put ‘addDefaults’ element which is responsible for generating all the remaining attributes.</p><pre class="brush: xml;"><?xml version='1.0'?><br /><info><br /> <element name='Info'><br /> <element name='routeInfos'><br /> <element name='routeInfo' property='routeInfos' updater='addRouteInfo'/><br /> </element><br /> <element name='entityInfos'><br /> <element name='entityInfo' property='entityInfos' updater='addEntityInfo'/><br /> </element><br /> <addDefaults/><br /> </element><br /></info></pre><br /><p>3. Create .betwixt files for any class using list/arraylist as there attributes.</p><br /><p>That’s all folks. Hopefully, this will save some time and effort to many. I had to dig through Betwixt testcases to come up with this along with the documents on there website.</p> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com1tag:blogger.com,1999:blog-36956413.post-28560760877713389802010-08-14T03:04:00.001-07:002010-08-14T03:05:30.837-07:00AspectJ - Pointcut and Advices for methods with more than one argumentsI am still doing my Masters in computer science and have worked on many new technologies like javaFX, Jasper Reports etc. But, I have not had time to update my blog on any of these. I was working on AspectJ for another project for my course, only to realize that Google is not able to find a blog that provides me the syntax to use Poitcut with parameters when there are more than one arguments to a method for which the join point is being defined. After lot of frustration, I found the solution in trial and error method. The AspectJ documentation only provides the trivial examples with functions having one arguments, for example - a setter. I went through a AOP textbook and even that failed to provide me this solution. Hopefully, this piece of information will come in Google search if anyone is searching solution of exact or similar problem. Since, its my school project I cant put the real code and project on the blog. I will consider a dummy situation here. Lets say that there exists a Class named Transaction, and there exist a method doInsert as shown below. <pre class="brush: java; collapse: false;"><br />public class Transaction{<br /> public Boolean doInsert(Integer accountNumber, Double amount){<br /> //.<br /> //.<br /> //.<br /> return boolVal;<br /> }<br />}<br /></pre><br /><br />To define a point cut for the same, the below aspect can be used.<br /><br /><pre class="brush: java; collapse: false;"><br /><br />public aspect TransactionAspect {<br /> pointcut t1(Transaction t, Integer accountNumber) : <br /> target(t) && args(accountNumber,..) && <br /> execution(* *(Integer,..));<br /><br /><br /> before(Transaction t, Integer accountNumber) : t1(t, accountNumber){<br /> if(!isDoAbleForAccount(t,accountNumber)){<br /> throw new RuntimeException("Error : Detected error before transacting");<br /> }<br /> }<br /> private boolean isDoAbleForAccount(Transaction t,Integer accountNumber){<br /> //<br /> //<br /> }<br />}<br /></pre><br /><br />The above aspect defines a pointcut named t1 with 2 parameters. The first parameter is the Transaction object itself which is the target object in execution, the second parameter is an Integer account number that is passed as an argument to the doInsert method. Note the syntax of the "args" in the pointcut. It has one to one mapping to the execution pattern "* *(Integer,..)" which defines any method, with any return value, but 1st argument as Integer and any number of remaining argument. The before advice uses this pointcut, and the remaing code is just not that important.<br /><br />There is a one to one mapping for arguments between the "args" and the pattern used to select method's from a target object. <br /><br /><br /> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com0tag:blogger.com,1999:blog-36956413.post-64998704402749014372009-08-28T22:48:00.001-07:002009-08-28T22:51:54.508-07:00javacvs – Netbeans lib to access CVS in java, Tutorial<p>Well, I din expect i will have a topic to write so soon after join for MS course. But, thanks to Dr. Li I had a chance to work with java again!!! I love java! So, here goes a tutorial which explains how to use javacvs lib. </p> <p>I will explain important snippets here, will provide the complete source at the end of the tutorial.</p> <p><strong>Step 1: </strong>Create the CVS Root object defining the meta data such as username, host etc To do this, we have to read the CVS/Root file. <pre class="brush: java;">File root = new File(cvsPath + PATH_DELIM + cvsRoot); <br /> if(true == root.exists()){<br /> BufferedReader bufferedReader = new BufferedReader(new FileReader(root));<br /> if(null != bufferedReader){<br /> System.out.println(MSG_CAPTURE_CVS_DETAIL);<br /> String cvsRootData = bufferedReader.readLine();<br /> CVSRoot rootData = CVSRoot.parse(cvsRootData);//Create the CVS Root object defining the meta data such as username, host etc</pre><br /><br /><strong>Step 2: </strong>Set the global options, needed for executing commands.<br /><pre class="brush: java;">GlobalOptions globalOptions = new GlobalOptions();<br />globalOptions.setCVSRoot(cvsRootData);</pre><br /><strong>Step 3: </strong>Obtain the connection with the credentials and the details provided and then open the connection.<br /><pre class="brush: java;">PServerConnection connection = new PServerConnection(rootData);<br />connection.open();</pre><br /><strong>Step 4:</strong> Create a client that can execute the cvs log command, and register the listener to be invoked for CVS outputs. The loglistener is explained at the end.<br /><pre class="brush: java;">Client client = new Client(connection, new StandardAdminHandler()); <br />client.setLocalPath(cvsPath);<br />client.getEventManager().addCVSListener(new LogListener());</pre><br /><strong>Step 5: </strong>The log command that does the job of getting the log for every file in the directory. The log builder is responsible to invoke fileinfo events on the loglistener.<br /><pre class="brush: java;">LogCommand command = new LogCommand();<br />command.setRecursive(true);<br />Builder builder = new LogBuilder(client.getEventManager(), command);<br />command.setBuilder(builder);</pre><br /><strong>Step 6: </strong>Initialize the writers, and execute the command, close the writers..<br /><pre class="brush: java;">xl = new File(F_OUTPUT_CSV);<br />xlWriter = new BufferedWriter(new FileWriter(xl));<br />xlWriter.write(HEADER);<br />client.executeCommand(command, globalOptions);<br />xlWriter.close();<br />bufferedReader.close();</pre><br /><strong><u>LogListener</u></strong><br />It extends BasicListener, and overrides the messageSent with an empty definition. It also overrides the fileInfoGenerated method, where the actual processing of each file is done. That is log each of  file. The snippet is below.<br /><pre class="brush: java;">//Handle control to Super class.<br /> super.fileInfoGenerated(fileinfoevent);<br /> <br />//Get the log information for the current file event.<br />LogInformation infoContainer = (LogInformation) fileinfoevent.getInfoContainer();<br />try {<br /> //Log to Excel in csv format.<br /> logToExcel(infoContainer);<br />} catch (IOException e) {<br /> //Just print trace, and try logging the next file event.<br /> e.printStackTrace();<br />}</pre><br />logToExcel is a simple method that writes the log to the file. The datastructure LogInformation has data related to the file being processed, all the revisions of the file, head revision, the message associated with each revision and lot of more details. I use only the ones I mentioned.<br />The complete code including the LogListener is below. Ignore the logic that is being done at logToExcel method, that some thing related to my work. But, the goal is to know that fileInfoEvent has LogInformation, that can be used to see data. You can write different listeners, builders in the similar way.<br /><pre class="brush: java; collapse: true;">/**<br /> * <br /> */<br />package com.ssb.nb.cvs;<br />/*<br /> * Java SE Packages<br /> */<br />import java.io.BufferedReader;<br />import java.io.BufferedWriter;<br />import java.io.File;<br />import java.io.FileInputStream;<br />import java.io.FileNotFoundException;<br />import java.io.FileReader;<br />import java.io.FileWriter;<br />import java.io.IOException;<br />import java.util.List;<br />import java.util.Properties;<br />import java.util.StringTokenizer;<br /><br />/*<br /> * Java CVS Packages<br /> */<br />import org.netbeans.lib.cvsclient.CVSRoot;<br />import org.netbeans.lib.cvsclient.Client;<br />import org.netbeans.lib.cvsclient.admin.StandardAdminHandler;<br />import org.netbeans.lib.cvsclient.command.Builder;<br />import org.netbeans.lib.cvsclient.command.CommandException;<br />import org.netbeans.lib.cvsclient.command.GlobalOptions;<br />import org.netbeans.lib.cvsclient.command.log.LogBuilder;<br />import org.netbeans.lib.cvsclient.command.log.LogCommand;<br />import org.netbeans.lib.cvsclient.command.log.LogInformation;<br />import org.netbeans.lib.cvsclient.command.log.LogInformation.Revision;<br />import org.netbeans.lib.cvsclient.commandLine.BasicListener;<br />import org.netbeans.lib.cvsclient.connection.AuthenticationException;<br />import org.netbeans.lib.cvsclient.connection.PServerConnection;<br />import org.netbeans.lib.cvsclient.event.FileInfoEvent;<br />import org.netbeans.lib.cvsclient.event.MessageEvent;<br /><br />/**<br /> * @author Shreyas Purohit<br /> *<br /> */<br />public class NBCvs {<br /> /*<br /> * Private Static variables<br /> */<br /> private static File xl;<br /> private static BufferedWriter xlWriter;<br /> private static String cvsPath = "F:/EWorkspace/org.eclipse.core.commands";<br /> private static String F_OUTPUT_CSV = "F:/output.csv";<br /> <br /> /*<br /> * Private Constants <br /> */<br /> private static final String APP_NAME = "CVS2l : ";<br /> private static final String MSG_METHOD = " method: ";<br /> private static final String MSG_USERNAME = " username: ";<br /> private static final String MSG_REPOSITORY = " repository: ";<br /> private static final String MSG_PORT = " port: ";<br /> private static final String MSG_HOST = "Using host: ";<br /> private static final String cvsRoot = "CVS/Root";<br /> private static final String PATH_DELIM = "/";<br /> private static final String MSG_EXIT_SUCCESS = APP_NAME + "Completed sucessfully, terminating process.";<br /> private static final String MSG_EXEC_LOG = APP_NAME + "Executing log command";<br /> private static final String MSG_INIT_OUTPUT = APP_NAME + "Initializing output file";<br /> private static final String MSG_CONNECTING_CVS = APP_NAME + "Connecting to CVS";<br /> private static final String MSG_CAPTURE_CVS_DETAIL = APP_NAME + "Capturing CVS metadata";<br /> private static final String MSG_LOADED_PROP = APP_NAME + "Loaded required properties";<br /> private static final String MSG_PROP_NOT_READABLE = APP_NAME + "Could not read tool.properties, make sure it is not being used by some other application";<br /> private static final String MSG_EXITING = APP_NAME + "Exiting..";<br /> private static final String MSG_PROP_NOT_FOUND = APP_NAME + "Could not find tool.properties, make sure it exists in same directory as the cvs2l.jar";<br /> private static final String TOOL_PROPERTIES = "tool.properties";<br /> private static final String MSG_INIT = APP_NAME + "Initializing";<br /> private static final String HEADER = "File Name,Package,Revision,Bug no.,Description\n";<br /> <br /> /*<br /> * Static block to load properties.<br /> */<br /> static{<br /> System.out.println(MSG_INIT);<br /> Properties prop = new Properties();<br /> try {<br /> prop.load(new FileInputStream(TOOL_PROPERTIES));<br /> } catch (FileNotFoundException e) {<br /> System.out.println(MSG_PROP_NOT_FOUND);<br /> splashExitMsg();<br /> systemExit();<br /> } catch (IOException e) {<br /> System.out.println(MSG_PROP_NOT_READABLE);<br /> splashExitMsg();<br /> systemExit();<br /> }<br /> cvsPath = prop.getProperty("CVS_PATH");<br /> F_OUTPUT_CSV = prop.getProperty("OUTPUT");<br /> System.out.println(MSG_LOADED_PROP);<br /> }<br /> <br /> /**<br /> * Displays exit message<br /> */<br /> private static void splashExitMsg() {<br /> System.out.println(MSG_EXITING);<br /> }<br /><br /> /**<br /> * Exits system<br /> */<br /> private static void systemExit() {<br /> System.exit(1);<br /> }<br /> <br /> /**<br /> * @param args<br /> * @throws IOException <br /> * @throws FileNotFoundException <br /> * @throws AuthenticationException <br /> * @throws CommandException <br /> */<br /> public static void main(String[] args) throws FileNotFoundException, IOException, AuthenticationException, CommandException {<br /> /*<br /> * Get the path to the CVS Root folder, connect to CVS and execute the log command to retrieve the <br /> * file revision, package, bug number and description. <br /> */<br /> File root = new File(cvsPath + PATH_DELIM + cvsRoot); <br /> if(true == root.exists()){<br /> BufferedReader bufferedReader = new BufferedReader(new FileReader(root));<br /> if(null != bufferedReader){<br /> System.out.println(MSG_CAPTURE_CVS_DETAIL);<br /> String cvsRootData = bufferedReader.readLine();<br /> CVSRoot rootData = CVSRoot.parse(cvsRootData);//Create the CVS Root object defining the meta data such as username, host etc<br /> <br /> GlobalOptions globalOptions = new GlobalOptions();<br /> globalOptions.setCVSRoot(cvsRootData); //Set the global options, needed for executing commands.<br /><br /> System.out.println(APP_NAME + MSG_HOST + rootData.getHostName() + MSG_PORT + rootData.getPort() + MSG_REPOSITORY + rootData.getRepository() + MSG_USERNAME + rootData.getUserName() + MSG_METHOD + rootData.getMethod());<br /> System.out.println(MSG_CONNECTING_CVS);<br /> PServerConnection connection = new PServerConnection(rootData);// Obtain the connection with the credentials and the details provided.<br /> connection.open(); //Open the connection.<br /> <br /> /*<br /> * Create a client that can execute the cvs log command, and register the listener to be invoked for CVS outputs.<br /> */<br /> Client client = new Client(connection, new StandardAdminHandler()); <br /> client.setLocalPath(cvsPath);<br /> client.getEventManager().addCVSListener(new LogListener());<br /> <br /> /*<br /> * The log command that does the job of getting the log for every file in the<br /> * directory.<br /> * The log builder is responsible to invoke fileinfo events on the loglistner.<br /> */<br /> LogCommand command = new LogCommand();<br /> command.setRecursive(true);<br /> Builder builder = new LogBuilder(client.getEventManager(), command);<br /> command.setBuilder(builder);<br /> <br /> /*<br /> * Initialize the writers, and execute the command, close the writers..<br /> */<br /> System.out.println(MSG_INIT_OUTPUT);<br /> xl = new File(F_OUTPUT_CSV);<br /> xlWriter = new BufferedWriter(new FileWriter(xl));<br /> xlWriter.write(HEADER);<br /> System.out.println(MSG_EXEC_LOG);<br /> client.executeCommand(command, globalOptions);<br /> xlWriter.close();<br /> bufferedReader.close();<br /> System.out.println(MSG_EXIT_SUCCESS);<br /> }<br /> }<br /> }<br /> /**<br /> * The Listener class for the log command to be executed.<br /> * <br /> * @author Shreyas Purohit<br /> *<br /> */<br /> public static class LogListener extends BasicListener{<br /> private static final String DOT_STR = ".";<br /> /*<br /> * Private static constants.<br /> */<br /> private static final String BUG_FOLLOWED_SPACE_STR = "Bug ";<br /> private static final String BUG_STR = "Bug";<br /> private static final String SPACE = " ";<br /> private static final String NEW_LINE = "\n";<br /> private static final String DOUBLE_COMMA = ",,";<br /> private static final String COMMA_STR = ",";<br /> private static final String EMPTY_STR = "";<br /> private static final String JAVA_STR = "java";<br /> private static final String SRC_STR = "src";<br /> private static final String BACKSLASH = "\\";<br /> private static final String SRC = "\\src\\";<br /><br /> @Override<br /> public void messageSent(MessageEvent e) {<br /> /*<br /> * Override the super class, so as to prevent from logging to console.<br /> */<br /> }<br /> <br /> @Override<br /> public void fileInfoGenerated(FileInfoEvent fileinfoevent) {<br /> //Handle control to Super class.<br /> super.fileInfoGenerated(fileinfoevent);<br /> <br /> //Get the log information for the current file event.<br /> LogInformation infoContainer = (LogInformation) fileinfoevent.getInfoContainer();<br /> try {<br /> //Log to Excel in csv format.<br /> logToExcel(infoContainer);<br /> } catch (IOException e) {<br /> //Just print trace, and try logging the next file event.<br /> e.printStackTrace();<br /> }<br /> }<br /> <br /> public void logToExcel(LogInformation info) throws IOException{<br /> System.out.print(NEW_LINE);<br /> System.out.println("------------------Processing---------------------------");<br /> System.out.println("File : " + info.getRepositoryFilename());<br /> System.out.println("Available Revisions...");<br /> <br /> String path = info.getFile().getPath();<br /> String strPackage = EMPTY_STR;<br /> /*<br /> * Extract package information from the path information. <br /> */<br /> if(path.indexOf(SRC) >= 0 ){<br /> String packagePath = path.substring(path.indexOf(SRC));<br /> StringTokenizer tokenizer = new StringTokenizer(packagePath,BACKSLASH);<br /> while(tokenizer.hasMoreTokens()){<br /> String nextToken = tokenizer.nextToken();<br /> if(!nextToken.equalsIgnoreCase(SRC_STR) && !(nextToken.indexOf(JAVA_STR)>=0)){<br /> strPackage += nextToken + DOT_STR;<br /> }<br /> }<br /> if(strPackage != EMPTY_STR){<br /> strPackage = strPackage.substring(0, strPackage.length()-1);<br /> }<br /> }<br /> boolean fileNameWritten = false; <br /> <br /> /*<br /> * Process all the revisions for the current file. <br /> */<br /> List<Revision> revisionList = info.getRevisionList();<br /> for(Revision revision : revisionList){<br /> System.out.print(revision.getNumber() + SPACE);<br /> //Remove new line character from the message else it hinders rendering in the Excel sheet.<br /> String message = (revision.getMessage() != null && revision.getMessage().indexOf(NEW_LINE) >= 0 ? revision.getMessage().replaceAll(NEW_LINE, SPACE) : revision.getMessage());<br /> String bugNumber = null;<br /> <br /> /*<br /> * Extract the bug number from the message string if any. <br /> */<br /> if(message.indexOf(BUG_FOLLOWED_SPACE_STR) >= 0){<br /> StringTokenizer tokenizer = new StringTokenizer(message,SPACE);<br /> while(tokenizer.hasMoreTokens()){<br /> if(BUG_STR.equalsIgnoreCase(tokenizer.nextToken())){<br /> if(bugNumber != null){<br /> bugNumber += SPACE + tokenizer.nextToken(); <br /> }else{<br /> bugNumber = tokenizer.nextToken();<br /> }<br /> if(null != bugNumber){<br /> bugNumber = bugNumber.replaceAll(COMMA_STR, SPACE);<br /> }<br /> }<br /> }<br /> <br /> }<br /> /*<br /> * Write to CSV, if there exist a bug in the message for this revision.<br /> */<br /> if(null != bugNumber){<br /> /*<br /> * Write the filename only once, and repeat the revisions for the same file.<br /> */<br /> if(false == fileNameWritten){<br /> xlWriter.write(info.getFile().getName() + COMMA_STR + strPackage + DOUBLE_COMMA+ NEW_LINE);<br /> fileNameWritten = true;<br /> }<br /> xlWriter.write(EMPTY_STR+ DOUBLE_COMMA + revision.getNumber() + COMMA_STR + (bugNumber != null ? bugNumber : SPACE) + COMMA_STR + message + NEW_LINE);<br /> }<br /> }<br /> }<br /> }<br />}</pre> SacrosanctBloodhttp://www.blogger.com/profile/01706617283053130814noreply@blogger.com1