<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Milen Dyankov&apos;s blog</title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/" />
    <link rel="self" type="application/atom+xml" href="http://milen.commsen.com/atom.xml" />
    <id>tag:milen.commsen.com,2010-04-05://1</id>
    <updated>2010-07-22T22:41:59Z</updated>
    <subtitle>Mostly technical notes and thoughts, hopefully useful for someone else as well</subtitle>
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type 5.01</generator>

<entry>
    <title>Just added &apos;J&apos; in front of WebThumb</title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2010/07/just-added-j-in-front-of-webthumb.html" />
    <id>tag:milen.commsen.com,2010://1.14</id>

    <published>2010-07-22T22:21:42Z</published>
    <updated>2010-07-22T22:41:59Z</updated>

    <summary>Yep, good guess, a Java API to bluga.net webthumb in now available. Making your Java application display website thumbnails is now something really easy to implement. Get your API KEY form bluga.net webthumb, download JWebThumb and start requesting and fetching...</summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="Software" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="api" label="API" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="gae" label="GAE" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="simple" label="Simple" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="thumbnail" label="thumbnail" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="xstream" label="XStream" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p>Yep, good guess, a Java API to <a href="http://webthumb.bluga.net">bluga.net webthumb</a> in now available. Making your Java application display website thumbnails is now something really easy to implement. Get your API KEY form <a style="text-decoration: none; outline-style: none; outline-width: initial; outline-color: initial; color: rgb(61, 123, 34); " href="http://webthumb.bluga.net">bluga.net webthumb</a>, download <a href="http://sourceforge.net/projects/jwebthumb/files/">JWebThumb</a> and start requesting and fetching thumbnails with just a few lines of code. &nbsp;&nbsp;</p> <div>&nbsp;</div> <div>Why&nbsp;<a style="text-decoration: underline; outline-style: none; outline-width: initial; outline-color: initial; color: rgb(61, 123, 34); " href="http://webthumb.bluga.net">bluga.net webthumb</a>? Nope, I'm not gonna tell you it's the best tool out there, having unique features, ... or any of this marketing bla bla. The truth is, it was the first tool I found, that met my requirements</div>     <ul><li>custom size thumbnails</li><li>support for both JPG an PNG</li><li>web services or REST based API</li><li>either creates thumbnail instantly or sends notification when done</li><li>free version&nbsp;</li></ul>]]>
        <![CDATA[<p>I gave it a try and it turned out it's quite fast and reliable. Not that I have been heavily using it, but so far I haven't had any problems with it. Most of the time when I request a thumbnail it estimates it will take about 20 second but in fact my servlet receives notification in less than 2 seconds.&nbsp;</p><p>&nbsp;</p><p>So, after a few days of playing with webthumb's API I had a pile of Java snippets testing different aspects of it. Organizing the chaos resulted in version 0.1 of <span style="font-family: 'Courier New'; "><b>JWebThumb</b></span> project. Added data model and error handling on the top of that and 0.2 version was ready to go public (under LGPL). As usual Maven helped create a <a href="http://jwebthumb.sourceforge.net/">project site</a> from where you can learn how to use <span style="font-family: 'Courier New'; "><b>JWebThumb</b></span>, the <a href="http://github.com/azzazzel/JWebThumb">source code</a> is on GitHub and <a href="http://sourceforge.net/projects/jwebthumb/files/">downloads</a> are available on SourceForge. &nbsp;If you find a bug or missing feature don't hesitate to <a href="http://github.com/azzazzel/JWebThumb/issues">create an issue</a>.&nbsp;</p><p>&nbsp;</p><p>I consider 0.2 an early beta version. It works but it's not extensively tested and may have bugs. It uses <a href="http://xstream.codehaus.org/">XStream</a> for XML serialization and deserialization thus you can not yet use it on <a href="http://appengine.google.com/">GAE</a>. So version 0.3 is already underway having&nbsp;<meta http-equiv="content-type" content="text/html; charset=utf-8"><a style="text-decoration: none; outline-style: none; outline-width: initial; outline-color: initial; color: rgb(61, 123, 34); " href="http://xstream.codehaus.org/">XStream</a>&nbsp;replaced by <a href="http://simple.sourceforge.net/">Simple</a> so it works on&nbsp;<meta http-equiv="content-type" content="text/html; charset=utf-8"><a style="text-decoration: none; outline-style: none; outline-width: initial; outline-color: initial; color: rgb(61, 123, 34); " href="http://appengine.google.com/">GAE</a>. But before I release it I would like to test it a bit more and perhaps add support for <a href="http://webthumb.bluga.net/apidoc#status">'status' requests</a> missing in 0.2. So stay tuned, it shouldn't &nbsp;take too long.</meta></meta></p>]]>
    </content>
</entry>

<entry>
    <title>More &quot;Simple&quot; than &quot;XStream&quot;</title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2010/07/more-simple-than-xstream.html" />
    <id>tag:milen.commsen.com,2010://1.13</id>

    <published>2010-07-19T22:56:21Z</published>
    <updated>2010-07-19T23:44:52Z</updated>

    <summary><![CDATA[I guess every Java developer dealing with JAVA/XML serialization/deserialization&nbsp;knows about XStream. I was using it for years until yesterday. What happened yesterday? I found out XStream dos not work out of the box with GAE. Well is's not exactly XStream's...]]></summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="Miscellaneous" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="tips and tricks" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="gae" label="GAE" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="simple" label="Simple" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="xml" label="XML" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="xstream" label="XStream" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p>I guess every Java developer dealing with JAVA/XML serialization/deserialization&nbsp;knows about <a href="http://xstream.codehaus.org">XStream</a>. I was using it for years until yesterday. What happened yesterday? I found out XStream dos not work out of the box with <a href="http://code.google.com/appengine/">GAE</a>. Well is's not exactly XStream's fault. A lot of stuff does not work properly with GAE due to its limitations and odd security restrictions. But my hope to quickly find patch/workaround, went away as soon as I realized the problem was reported to XStream over an year ago&nbsp;(<a class="moz-txt-link-freetext" style="text-decoration: underline; outline-style: none; outline-width: initial; outline-color: initial; color: rgb(61, 123, 34); " href="http://jira.codehaus.org/browse/XSTR-566">http://jira.codehaus.org/browse/XSTR-566</a>) and there is still no good solution.&nbsp;</p> <p><meta http-equiv="content-type" content="text/html; charset=utf-8" /></p> <p>&nbsp;</p> <p>This way I was forced to look for alternatives. And I found <a href="http://simple.sourceforge.net/">Simple</a>! Conceptually it's a very similar to XStream. Serialization is really simple to use and revolves around several annotations and a single persister object. I got the impression it's noticeably faster than XStream. It's feature list is quite long (it even claims to be bean version tolerant) but so far I've used the standard stuff like converters, transformers, persister, etc.</p>  <p>However since &quot;Simple&quot;</p> <ul>     <li><span class="Apple-style-span" style="color: rgb(0, 0, 0); font-family: verdana, geneva, arial, sans-serif; font-size: 12px; -webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px; ">does not depend on 3rd party libraries</span></li>     <li><span class="Apple-style-span" style="color: rgb(0, 0, 0); font-family: verdana, geneva, arial, sans-serif; font-size: 12px; -webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px; ">is available in central Maven repository</span></li>     <li><span class="Apple-style-span" style="color: rgb(0, 0, 0); font-family: verdana, geneva, arial, sans-serif; font-size: 12px; -webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px; ">works out of the box with GAE</span></li>     <li><span class="Apple-style-span" style="color: rgb(0, 0, 0); font-family: verdana, geneva, arial, sans-serif; font-size: 12px; -webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px; ">is capable of doing everything XStream is doing &nbsp;</span></li> </ul> <p>&nbsp;</p> <p>it's about to become my number one XML serialization/deserialization tool. At least until I discover it's dark sides.</p>]]>
        
    </content>
</entry>

<entry>
    <title>Liferay Portal 6 Enterprise Intranets review</title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2010/07/liferay-portal-6-enterprise-intranets-review.html" />
    <id>tag:milen.commsen.com,2010://1.12</id>

    <published>2010-07-04T07:56:19Z</published>
    <updated>2010-07-04T08:22:28Z</updated>

    <summary><![CDATA[ A few weeks ago I was asked by Packt publishing to review the new Liferay Portal 6 Enterprise Intranets book. Going through over 650 pages took me some time but finally I'm ready to share my thought about it.&nbsp;...]]></summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="Liferay" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="book" label="book" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="liferay" label="Liferay" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p><img alt="" align="right" src="https://www.packtpub.com/sites/default/files/imagecache/productview/0387_Liferay%20Portal%206%20Enterprise%20Intranets.jpg" /></p> <p>A few weeks ago I was asked by <a href="http://www.packtpub.com">Packt publishing</a> to review the new <a href="https://www.packtpub.com/liferay-portal-6-enterprise-intranets/book">Liferay Portal 6 Enterprise Intranets</a> book. Going through over 650 pages took me some time but finally I'm ready to share my thought about it.&nbsp;</p> <p>By now you are probably scanning the text for something like &quot;In general this is ____ book&quot;. Don't bother, I'm not going to generalize in this post. In fact, what you put in place of ____ &nbsp;depends on who you are, what is your Liferay background, and what you expect to learn.</p> <div>&nbsp;</div>]]>
        <![CDATA[<p>I'm a software developer and I have been using Liferay for a few years now. When I heard about the book, my imagination draw a visions of me learning &quot;how Liferay internal mechanisms work&quot; or &quot;what some of those strange configuration parameters are used for&quot; or &quot;how to tweak portlets' functionality and performance&quot; or ... Of course, none of this had happened! This does not mean the book is bad, it only means I had too big expectations. If you are like me, then you may even get bored trying to read it form side to side.&nbsp;</p><p><meta http-equiv="content-type" content="text/html; charset=utf-8"><b>One thing that I really miss in this book is pointing out what's new in Liferay 6</b>. I know well 5.2.x version and I would appreciate a special style or sign indicating new&nbsp;<i>(or updated)</i>&nbsp;feature in Liferay 6. This way more advanced readers could concentrate on things that are potentially of their interest and skip the part they are already familiar with. &nbsp;If it wasn't for the review I would probably scan through the first few chapters for things I don't know <i>(and yes there are some interesting features to learn even if you already know Liferay)</i> and then pay more attention at the last ones.&nbsp;&nbsp;The good news is the <b>chapter 11 called &quot;</b><a href="https://docs.google.com/viewer?url=https%3A%2F%2Fwww.packtpub.com%2Fsites%2Fdefault%2Ffiles%2F0387-Chapter-11-Ongoing-Admin-Tasks.pdf"><b>Ongoing Admin Tasks</b></a><b>&quot; is freely available online</b> &nbsp;so you can have a look and see how it compares to the level of your knowledge. &nbsp;</meta></p><p><meta http-equiv="content-type" content="text/html; charset=utf-8" /></p><p>&nbsp;</p><p>But since&nbsp;<u><i>&quot;This book is for system administrators or experienced users (not necessarily programmers) who want to install and use Liferay in their teams or businesses without dealing with complex code. Prior knowledge of Liferay is not expected for this book.&quot;</i></u><i><b>&nbsp;</b></i>I decided to pretend I know almost nothing about Liferay and simply followed the instructions in the book <i>(I wish I had the PDF version, it would save me some typing)</i>. When I got rid of <i>&quot;come on, I already know all this&quot;</i> attitude, I had to admit the book is quite well organized.<b> Each chapter typically starts with explaining the basic functionality then goes through some configuration options and ends up instructing the reader how to define appropriate permissions.</b> Sure, after the first two chapters you already know the pattern for defining permissions and there is no need to repeat it over and over again. But on the other hand if you intend to later one use the book as a quick reference or cheat-sheet, it may come handy. &nbsp;&nbsp;</p><p><b><br /></b></p><p><b>The book will explain almost </b><i>(as with every book the thing you are particularly interested in, will be probably missing)</i><b> every intranet feature you may need.</b> Moreover it sometimes goes a bit off topic as I can hardly imagine configuring WAP or SEO for intranet site. It will not give too much details though, only enough to get you started and sometimes make small customizations. For example, it will not tell you how exactly portlet properties affect Liferay, but will give you a list of all properties related to current component with short explanation.&nbsp;</p><p><b>Being frequent reader of Liferay forums I'm convinced there is need for such book.</b> The number of questions starting with &quot;How can I ...&quot; speaks for itself and from what I've noticed people are already referring to this book for answers. <b>But even if you are more advanced Liferay user, you may still find in the book valuable information about open search, clustering, reporting and audit logging, integration with Alfresco, CMIS and many others.</b> Just remember, don't expect too much details. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</p><div>&nbsp;</div>]]>
    </content>
</entry>

<entry>
    <title>Writing Liferay portlet to display a file in a way &quot;tail -f&quot; does  </title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2010/05/writing-liferay-portlet-to-display-a-file-in-a-way-tail--f-does.html" />
    <id>tag:milen.commsen.com,2010://1.11</id>

    <published>2010-05-03T22:10:42Z</published>
    <updated>2010-07-04T08:27:10Z</updated>

    <summary><![CDATA[ Don't know about you but I can't imagine debugging enterprise class applications without having &quot;tail -f /path/to/log.file&quot; running in dedicated console window. During development and testing phases (assuming work is done &quot;in house&quot;) there is usually no problem with...]]></summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="Liferay" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="tips and tricks" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="java" label="Java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="javascript" label="javascript" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="jquery" label="jquery" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="liferay" label="Liferay" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="portlet" label="portlet" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="weakreferences" label="weak references" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p><meta http-equiv="content-type" content="text/html; charset=utf-8" /></p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">Don't know about you but I can't imagine debugging enterprise class applications without having &quot;<span style="font-family: 'Courier New'; ">tail -f /path/to/log.file</span>&quot; running in dedicated console window. During development and testing phases (assuming work is done &quot;in house&quot;) there is usually no problem with this approach as the whole team have access to servers' log files. This is not always the case with staging and production environments though. These days a lot of companies execute strong security policies which sometimes means that application is only accessible via HTTP. In such case, depending on how you SLA looks like, &quot;<i>log files provided on demand via e-mail or FTP</i>&quot; may not be an option.&nbsp;</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">Facing this kind of problem in recent Liferay based project, made me think about creating a portlet capable of displaying log files. Something like WWW based version of &quot;tail -f&quot;. This is how Tailgate was born (for those of you looking for solution here is <a href="http://github.com/azzazzel/Liferay-plugins/downloads">download page</a>). The rest of this post will concentrate on explaining why it was not &quot;<i>a max 2h of coding</i>&quot; as I thought in the begging.</p>]]>
        <![CDATA[<p style="text-align: justify; ">Tailgate portlet needs to dynamically show new lines as they are written to the file without reloading the whole portal page. Assuming one portlet instance is configured to display only one file, the solution appears to be straightforward and rather simple: <meta http-equiv="content-type" content="text/html; charset=utf-8" /></p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; "><code class="blockWhite">while page is displayed {<br /> &nbsp;browser sends AJAX request<br /> &nbsp;server checks if there are new lines in given file &nbsp;<br /> &nbsp;server responds with list of new lines<br /> &nbsp;browser adds lines to appropriate DOM element<br /> }</code>While this is in general the whole functionality, there are a few things to consider on both front-end (client) and back-end (server) side. &nbsp;</p> <h2><b><span style="font-size: large; ">front-end</span></b></h2> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">As far as front-end is concern there are 2 main things to consider:&nbsp;</p> <h3><span style="font-size: small; "><u><b>Multiple instances on the same page</b></u></span></h3> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">This is a bit tricky and requires good understanding of how portals work. When a portlet is developed it contains common code for all instances. So if multiple instance can be placed on the same portal page then the common code should be able to distinguish instances. This is even more important with AJAX requests. &nbsp;&nbsp;</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">In this case we have a common logic for sending and receiving AJAX requests and updating DOM model. However when portlet instance sends AJAX requests it needs to provide instance specific set of parameters as part of the URL. Also when response is received only DOM elements belonging to this particular instance need to be updated.</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">The common JavaScript code is provided in <span style="font-family: 'Courier New'; ">tailgate.js</span> file. The portlet uses also <a href="http://plugins.jquery.com/project/timers">jQuery timers module</a> (<span style="font-family: 'Courier New'; ">jquery.timers.js</span>) which provides high level abstraction of setTimeout and setInterval. These files are part of the portlet code. Including them in <span style="font-family: 'Courier New'; ">HTML</span> page is done by adding&nbsp;</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "><code class="blockWhite">&lt;header-portlet-javascript&gt;/js/jquery.timers.js&lt;/header-portlet-javascript&gt;<br /> &lt;header-portlet-javascript&gt;/js/tailgate.js&lt;/header-portlet-javascript&gt;</code></p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">to <span style="font-family: 'Courier New'; ">WEB-INF/liferay-portlet.xml</span>. This way Liferay will add appropriate links in <span style="font-family: 'Courier New'; ">HEAD</span> section of <span style="font-family: 'Courier New'; ">HTML</span> page and will do this only once regardless of how many portlet instances page contains.</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">The <span style="font-family: 'Courier New'; ">tailgate.js</span>&nbsp;defines the Tailgate object and&nbsp;<span style="font-family: 'Courier New'; ">tailgateInstances</span>&nbsp;map for holding information about Tailgate instances:</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "><code class="blockWhite">var tailgateInstances = new Array();&nbsp;<br /> function Tailgate(lines, url) {<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>this.lines=lines<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>this.url=url<br /> }</code></p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">When portlet instance is rendered it takes care to prefix DOM element ids with it's namespace</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "><code class="blockWhite">&lt;button id=&quot;&lt;portlet:namespace /&gt;_start&quot;&gt;Start&lt;/button&gt;<br /> &lt;button id=&quot;&lt;portlet:namespace /&gt;_stop&quot;&gt;Stop&lt;/button&gt;<br /> ...<br /> &lt;ul id=&quot;&lt;portlet:namespace /&gt;list&quot; class=&quot;tailgate&quot;&gt;&nbsp;</code></p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">, add itself to <span style="font-family: 'Courier New'; ">tailgateInstances</span>&nbsp;map &nbsp;&nbsp;</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "><code class="blockWhite">&lt;script type=&quot;text/javascript&quot;&gt;<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>tailgateInstances[&quot;&lt;portlet:namespace /&gt;&quot;] =<br /> &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new Tailgate(&lt;%=prefs.getValue(&quot;lines&quot;,&quot;100&quot;)%&gt;, &quot;&lt;liferay-portlet:resourceURL /&gt;&quot;);&nbsp;</code></p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">&nbsp;and pass the namespace to called functions</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "><code class="blockWhite"><span class="Apple-tab-span" style="white-space: pre; ">	</span>jQuery(&quot;#&lt;portlet:namespace /&gt;_start&quot;).click(function(){<span class="Apple-tab-span" style="white-space: pre; ">		</span>startReading(&quot;&lt;portlet:namespace /&gt;&quot;);<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>})<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>jQuery(&quot;#&lt;portlet:namespace /&gt;_stop&quot;).click(function(){<br /> <span class="Apple-tab-span" style="white-space: pre; ">		</span>stopReading(&quot;&lt;portlet:namespace /&gt;&quot;);<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>})<br /> &lt;/script&gt;&nbsp;</code></p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">Note that all functions in <span style="font-family: 'Courier New'; ">tailgate.js</span> are designed to accept namespace as parameter. This way when called they can either get appropriate Tailgate instance from <span style="font-family: 'Courier New'; ">tailgateInstances</span> map and then check for given property (for example url), or find and update DOM elements prefixed with this namespace.&nbsp;</p> <h3><u><b>Control the CPU and memory usage</b></u></h3> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">Monitored file can change very fast (application could add few megabytes is just a second) therefore at first it seems to be very important to query back-end as often as possible. &nbsp;This however results in very high CPU usage. In fact going down below 10ms may force you to kill your browser in order to recover your system. Experimenting with different values I finally decided that refresh rate of one second is a reasonable compromise.&nbsp;</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">Another thing is memory. If lines are only added but never deleted then after a while the browser will be trying to display a tens of megabytes of <span style="font-family: 'Courier New'; ">HTML</span> code. Therefore Tailgate is configured to only display last <span style="font-family: 'Courier New'; ">X</span> lines. It renders each line as <span style="font-family: 'Courier New'; ">&lt;li&gt;</span> element and when new line is added, a new <span style="font-family: 'Courier New'; ">&lt;li&gt;</span> element is added to the parent <span style="font-family: 'Courier New'; ">&lt;ul&gt;</span>. Then it checks the size of <span style="font-family: 'Courier New'; ">&lt;ul&gt;</span> and if it's bigger than <span style="font-family: 'Courier New'; ">X</span>, old lines are deleted from DOM model. Luckily <a href="http://jquery.com/">jQuery</a> comes with convenient methods so this is really easy to implement:&nbsp;</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "><code class="blockWhite"><span class="Apple-tab-span" style="white-space: pre; ">	</span>jQuery('#'+namespace+'list').append(data);<br /> &nbsp;<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>var maxLines = tailgateInstances[namespace].lines;<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>var lines = jQuery('#' + namespace + 'list li').length;<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>if (lines &gt; maxLines) {<br /> <span class="Apple-tab-span" style="white-space: pre; ">		</span>jQuery('#' + namespace + 'list li').slice(0, lines - maxLines).remove();<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>}</code></p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">&nbsp;</p> <h2><b><span style="font-size: large; ">back-end</span></b></h2> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">The line <i>&quot;server checks if there are new lines in given file&quot;</i> in the above pseudo algorithm is also way oversimplified.&nbsp;</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">The <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/io/RandomAccessFile.html">RandomAccessFile</a>&nbsp;class provides the functionality to position at specific place in file and start reading. However creating a new instance on every request is not a very smart thing to do. Even if it is somehow cached per portlet instance, there still could be many instances monitoring the same file <i>(in the future each may provide different filtering, highlighting, etc.)</i> Also, depending on what file system is used <i>(for example old versions of NFS)</i>, there may be some locking issues when multiple threads try to access the given file at the same time. Therefore it looks like the optimal solution would be to have one thread continuously reading the file and updating in memory buffers provided by specific instances. Here is the activity diagram: &nbsp;</p> <p><img alt="Tailgate activity diagram" width="700" src="http://milen.commsen.com/TailgateActivityDiagram.png" /></p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">This looks simple enough but again there are a couple of things to think about:</p> <h3><u><b>synchronization</b></u></h3> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">Since there are two thread operating on the same buffer (one writing and one reading) the buffer's read and write operations need to be synchronized. Otherwise a typical <a href="http://java.sun.com/docs/books/tutorial/essential/concurrency/memconsist.html">Memory Consistency Errors</a>&nbsp;may occur. &nbsp;</p> <h3><u><b>buffer size</b></u></h3> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">Buffers need to have fixed size in order to avoid memory leaks! Since the front-end also displays a limited amount of data, the same configuration parameter can be used to define both the number of lines displayed and buffered. Then every time a new line is added to the the oldest one is removed if buffer the size is reached:</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "><code class="blockWhite"><span class="Apple-tab-span" style="white-space: pre; ">	</span>public boolean addLine(final String line) {<br /> <span class="Apple-tab-span" style="white-space: pre; ">		</span>boolean result;<br /> <span class="Apple-tab-span" style="white-space: pre; ">		</span>synchronized (buffer) {<br /> <span class="Apple-tab-span" style="white-space: pre; ">			</span>result = buffer.add(line);<br /> <span class="Apple-tab-span" style="white-space: pre; ">			</span>if (buffer.size() &gt; maxSize) {<br /> <span class="Apple-tab-span" style="white-space: pre; ">				</span>buffer.remove();<br /> <span class="Apple-tab-span" style="white-space: pre; ">			</span>}<br /> <span class="Apple-tab-span" style="white-space: pre; ">		</span>}<br /> <span class="Apple-tab-span" style="white-space: pre; ">		</span>return result;<br /> <span class="Apple-tab-span" style="white-space: pre; ">	</span>}&nbsp;</code></p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">&nbsp;</p> <h3><u><b>when to stop reading</b></u></h3> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">If you have carefully examined the activity diagram above you may have noticed that buffers are never unregistered. This is not an error, it is simply impossible to tell when a buffer is no longer needed. One may argue that user could send us appropriate message by clicking on something saying <i>&quot;I'm done watching, please stop the buffer!&quot;</i>. My experience shows such approach is often misunderstood and misused <i>(by that I mean used too often or not at all)</i>. So how can one prevent <span style="font-family: 'Courier New'; ">FileMonitor</span> thread from running forever<i> (it will run as long as there are buffers)</i>? Let the garbage collector do it's job!&nbsp;</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">Buffers are stored in portlet sessions, so instead of relaying on user interaction, Tailgate relays on buffers being garbage collected once portlet session is closed. As you probably know an object becomes eligible for garbage collection when there are no hard references to it. Is this the case with buffers? As the diagram shows, buffers are referenced in 2 other places (marked in red) - <span style="font-family: 'Courier New'; ">FileMonitoringEngine</span> needs to keep track of which buffer is assigned to which monitor and <span style="font-family: 'Courier New'; ">FileMonitor</span> needs to maintain a list of buffers to write to. But here is the difference, in order to leverage the garbage collector's ability to determine buffers' reachability, both <span style="font-family: 'Courier New'; ">FileMonitoringEngine</span> and <span style="font-family: 'Courier New'; ">FileMonitor</span> use weak references. <span style="font-family: 'Courier New'; ">FileMonitoringEngine</span> uses in fact standard WeakHashMap to store the mapping. &nbsp;<span style="font-family: 'Courier New'; ">FileMonitor</span> only needs a Set of weak references but there is no WeakHashSet class. In JDK 6 there is a convenient &quot;<span style="font-family: 'Courier New'; ">newSetFromMap(Map&lt;E,Boolean&gt; map)</span>&quot; method available in <span style="font-family: 'Courier New'; ">java.util.Collections</span> class. In order to be compatible with JDK 5 the behavior of this the JDK 6 method had to be implemented as part of Tailgate portlet.&nbsp;</p> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">This way as soon as portlet session gets garbage collected there are no hard references to the buffer and it is collected as well. When all buffers are garbage collected the <span style="font-family: 'Courier New'; ">FileMonitor</span> thread ends. The first request instantiating new buffer will start new <span style="font-family: 'Courier New'; ">FileMonitor</span> thread which will again run as long as there is someone interested in receiving results.&nbsp;</p> <h2><b><span style="font-size: medium; ">Conclusion</span></b></h2> <p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: justify; ">I'm well aware I'm not writing anything really revealing here. But after spending some hours on Tailgate portlet I thought I would write about the problems and solutions. Hopefully you at least learned a little something from this experience. In case you are interested,&nbsp;<a href="http://github.com/azzazzel/Liferay-plugins/tree/master/tailgate/">Tailgate's source code is available at GitHub.</a>&nbsp;</p>]]>
    </content>
</entry>

<entry>
    <title>Custom global markup portlet</title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2010/04/custom-global-markup-portlet.html" />
    <id>tag:milen.commsen.com,2010://1.10</id>

    <published>2010-04-05T22:40:01Z</published>
    <updated>2010-07-04T08:27:55Z</updated>

    <summary><![CDATA[What would you do if a customer demands to &quot;integrate&quot; his Liferay based corporate portal with Google Analytics, Geminus, ClickTale, Crazy Egg,&nbsp; and whole bunch of other analytics tools available out there?As you probably know, such services typically provide some...]]></summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="Liferay" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="tips and tricks" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="css" label="css" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="html" label="html" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="javascript" label="javascript" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="liferay" label="Liferay" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="portlet" label="portlet" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p>What would you do if a customer demands to &quot;integrate&quot; his Liferay based corporate portal with <a href="http://www.google.com/analytics">Google Analytics</a>, <a href="http://www.gemius.com/">Geminus</a>, <a href="http://www.clicktale.com/">ClickTale</a>, <a href="http://www.crazyegg.com/">Crazy Egg</a>,&nbsp; and whole bunch of other analytics tools available out there?</p><p>As you probably know, such services typically provide some piece of javascript (code or file) which needs to be added to every page of monitored web site. Each service also provides unique customer code/key (which is either already part of the javascript provided or needs to be placed in specific place). Regardless of whether using all of them at the same time is a smart thing to do, there are a few technical problems to solve:</p><ul><li>How to add custom code to every portal page</li><li>How to deal with unique codes/keys through development, testing, staging, production phases</li><li>How to minimize the impact of changing/removing custom code in production environment</li></ul>]]>
        <![CDATA[<p>There are few ways to solve the first case</p> <p><u><b>Make the code part of the theme. </b></u><br /> <br /> This is easy to do but it has some drawbacks. First of all, depending on how theme is applied, the code may end up on every page in every community or only in few rarely visited pages. Also if your portal uses a number of themes than you need to either make a common theme and make the rest extend it, or you need to add it to each theme. This approach may be a serious maintenance challenge. <br /> <br /> You may solve the problem with unique codes/keys by using portal properties but you'll not be able to easily modify or remove the java script code if you have disabled (and you should) hot deployment on production servers.&nbsp; <br /> <br /> <u><b><br /> Create custom portlet and add it to every page. </b></u><br /> <br /> Samuel Kong's excellent post <a href="http://www.liferay.com/web/samuel.kong/blog/-/blogs/adding-a-javascript-to-every-page">http://www.liferay.com/web/samuel.kong/blog/-/blogs/adding-a-javascript-to-every-page</a>&nbsp; explains how to add javascript to every page. However be aware that if you blindly follow the example you'll add the code to EVERY page (not every page in given community). Following this approach you'll have to choose between two options</p> <ul>     <li>one portlet having all javascript codes</li>     <li>separate portlet per javascript code (or group of codes)</li> </ul> <p>The first one is only acceptable if all javascript codes can ALWAYS be placed together and it kind of violates the &quot;design by responsibility&quot; concept. The second approach on the other hand deploys a lot of boilerplate code which in some cases may have&nbsp; impact on performance. <br /> Unique codes/keys may be provided in portlet preferences but still it's not very convenient when portlets are automatically added via <code>layout.static.portlets.all</code> property. Also, as you have probably guessed, this approach does not solve the problem with modifying/removing javascript code in production environment.</p> <p><br /> <u><b>Use custom-global-markup-portlet<br /> </b></u><br /> custom-global-markup-portlet was written to solve all of the problems described above. The portlet is based on Samuel Kong's example, but it also provides convenient management interface in Liferay's control panel:</p> <p><a href="http://milen.commsen.com/customglobalmarkup/CustomGlobalMarkupConfig.png"><img width="700" alt="Custom Global Markup Portlet Configuration" src="http://milen.commsen.com/customglobalmarkup/CustomGlobalMarkupConfig.png" /></a></p> <p>&nbsp;</p> <p>As you can see from the above screenshot&nbsp;portal administrator can easly add/modify/delete any markup (javascript, CSS, HTML, ...). Here is how the portal look like after you save the above markups:</p> <p><a href="http://milen.commsen.com/customglobalmarkup/CustomGlobalMarkupResult.png"><img alt="Portal changed via Custom Global Markup Portlet" width="700" src="http://milen.commsen.com/customglobalmarkup/CustomGlobalMarkupResult.png" /></a></p> <p>As you may have already noticed there are a few important features. First of all the markup can be divided into multiple entries and each entry can be enabled/disabled and placed on top (in &lt;head&gt; section) or bottom (before &lt;/html&gt;) of the page. All entries are persisted into database which eliminates potential problems with maintaining different portlet preferences in different environments (development, staging, production). Also note that custom-global-markup-portlet is community scoped, which allows adding markup to pages of specific community.&nbsp; <br /> &nbsp;&nbsp;&nbsp;&nbsp; <br /> You can download the latest version of custom-global-markup-portlet here: <a href="http://github.com/azzazzel/Liferay-plugins/downloads">http://github.com/azzazzel/Liferay-plugins/downloads</a>.&nbsp;It is part of <a href="http://github.com/azzazzel/Liferay-plugins">Commsen Lifery plugins</a> which is free and open source project hosted at <a href="http://github.com">GitHub</a> and released under <a href="http://www.gnu.org/licenses/lgpl-2.1.html">LGPL license</a>. It is developed with <a href="http://github.com/azzazzel/liferay-maven-sdk">liferay-maven-sdk</a> and uses <a href="http://git-scm.com/">Git SCM</a><br /> &nbsp;</p> <p>&nbsp;</p>]]>
    </content>
</entry>

<entry>
    <title>Creating Liferay portlet with liferay-maven-sdk</title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2009/10/creating-liferay-portlet-with-liferay-maven-sdk.html" />
    <id>tag:milen.commsen.com,2009://1.9</id>

    <published>2009-10-09T21:47:50Z</published>
    <updated>2010-07-04T08:36:09Z</updated>

    <summary>This post will demonstrate how liferay-maven-sdk can be employed to build a Liferay portlet using Liferay&apos;s Service Builder feature. For this purpose we will create service-builder-portlet which is capable of displaying a list of players and adding a new player...</summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="Liferay" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="liferay" label="Liferay" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="maven" label="Maven" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="portlet" label="portlet" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="servicebuilder" label="service builder" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p>This post will demonstrate how <a href="http://github.com/azzazzel/liferay-maven-sdk">liferay-maven-sdk</a> can be employed to build a Liferay portlet using Liferay's Service Builder feature. For this purpose we will create service-builder-portlet which is capable of displaying a list of players and adding a new player to this list. The model, persistence layer and data access services will be generated by <code>Service Builder</code>.<br /> But first things first. Download and install&nbsp; <code>liferay-maven-sdk</code> if you haven't done so already (have a look at&nbsp; &quot;<a href="http://wiki.github.com/azzazzel/liferay-maven-sdk/download-and-install">Download and Install</a>&quot; page for instructions). Once <code>liferay-maven-sdk</code> is installed in your local repository, you can create the portlet.</p>]]>
        <![CDATA[<p>So enter the folder (or create one) where you keep your portlets and execute</p> <p><code class="blockWhite"> mvn archetype:generate </code></p> <p>you should see a list of available archetypes starting with</p> <p><code class="blockWhite">  Choose archetype:<br /> 1: local -&gt; liferay-portlet-archetype (Liferay portlet archetype)<br /> 2: local -&gt; liferay-theme-archetype (Liferay theme archetype)<br /> 3: internal -&gt; appfuse-basic-jsf (AppFuse archetype for creating a web application with Hibernate, Spring and JSF)<br /> 4: internal -&gt; appfuse-basic-spring (AppFuse archetype for creating a web application with Hibernate, Spring and Spring MVC)<br /> ...<br /> Choose a number:&nbsp; (1/2/3/4/...)&nbsp; :1</code></p> <p>Type <code>1</code> and press enter to choose <code>liferay-portlet-archetype. Then provide <code>groupId</code>, <code>artifactID</code>, <code>package</code> and <code>version</code>. For example:</code></p> <p><code class="blockWhite">  Define value for groupId: : com.commsen.liferay.examples.portlet.servicebuilder<br /> Define value for artifactId: : service-builder-portlet<br /> Define value for version:&nbsp; 1.0-SNAPSHOT: : 1.0 <br /> Define value for package:&nbsp; com.commsen.liferay.examples.portlet.servicebuilder: :</code></p> <p>Additionally you may execute&nbsp;</p> <p><code class="blockWhite"> mvn eclipse:eclipse </code></p> <p>to setup Eclipse IDE if, that is what you are using! At this time your portlet skeleton is ready and you may compile it and even create WAR, but it of course does nothig special.</p> <p>Let's now add data model and services. Create file <code>service-builder-portlet/src/main/webapp/WEB-INF/service.xml</code> :</p> <p><code class="blockWhite">  &lt;?xml version=&quot;1.0&quot;?&gt;<br /> &lt;!DOCTYPE service-builder PUBLIC &quot;-//Liferay//DTD Service Builder 5.2.0//EN&quot; &quot;http://www.liferay.com/dtd/liferay-service-builder_5_2_0.dtd&quot;&gt;<br /> <br /> &lt;service-builder package-path=&quot;com.commsen.liferay.examples.portlet.servicebuilder&quot;&gt;<br /> &nbsp;&nbsp; &nbsp;&lt;namespace&gt;SB&lt;/namespace&gt;<br /> &nbsp;&nbsp; &nbsp;&lt;entity name=&quot;Player&quot; local-service=&quot;true&quot; remote-service=&quot;true&quot;&gt;<br /> <br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;!-- PK fields --&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &lt;column name=&quot;playerId&quot; type=&quot;long&quot; primary=&quot;true&quot; /&gt;<br /> <br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;!-- Other fields --&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &lt;column name=&quot;name&quot; type=&quot;String&quot; /&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;column name=&quot;active&quot; type=&quot;boolean&quot; /&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;column name=&quot;score&quot; type=&quot;int&quot; /&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;column name=&quot;birthday&quot; type=&quot;Date&quot; /&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;column name=&quot;description&quot; type=&quot;String&quot; /&gt;<br /> <br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;!-- Order --&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &lt;order by=&quot;asc&quot;&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;order-column name=&quot;name&quot; /&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;/order&gt;<br /> <br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;!-- Finder methods --&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &lt;finder name=&quot;ActivePlayers&quot; return-type=&quot;Collection&quot;&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;finder-column name=&quot;active&quot; /&gt;<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;/finder&gt;<br /> &nbsp;&nbsp; &nbsp;&lt;/entity&gt;<br /> &lt;/service-builder&gt;</code></p> <p>The portlet projects created by liferay-portlet-archetype contain maven profile with lifray-maven-plugin&rsquo;s <code>build-service</code> goal attached to <code>generate-sources</code> phase. To run ServiceBuilder you need to activate the profile:</p> <p><code class="blockWhite"> mvn -P build-service package</code></p> <p>If you read carefully the messages you will realize that service and persistence classes were generated in <code>service-builder-portlet/src/main/java-service-api/</code> folder. Also there should be a few implementation files in <code>service-builder-portlet/src/main/java/</code>.</p> <p>Now we'll add custom methods. Open the following file <code> service-builder-portlet/src/main/java/com/commsen/liferay/examples/portlet/servicebuilder/service/impl/PlayerLocalServiceImpl.java</code> and paste this code:</p> <p><code class="blockWhite"> public void addPlayer(String name, boolean active, int score, Date birthday, String desc) throws PortalException, SystemException {<br /> <br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;long playerId = CounterLocalServiceUtil.increment();<br /> <br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Player player = PlayerUtil.create(playerId);<br /> <br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;player.setName(name);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;player.setActive(active);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;player.setScore(score);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;player.setBirthday(birthday);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;player.setDescription(desc);<br /> <br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;PlayerUtil.update(player, false);<br /> &nbsp;&nbsp; &nbsp;}<br /> <br /> <br /> &nbsp;&nbsp; &nbsp;public List&lt;Player&gt; getAllPlayers() throws PortalException, SystemException {<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return PlayerUtil.findAll();<br /> &nbsp;&nbsp; &nbsp;} </code></p> <p>We added methods to the implementation class. The next time we compile the code we need to activate the <code>build-service</code> profile again in order for <code>Service Builder</code> to react on the change and regenerate the API and interfaces. If you want to try it now simply execute</p> <p><code class="blockWhite"> mvn -P build-service compile</code></p> <p>Now we can start using these services in our portlet. Open<code> service-builder-portlet/src/main/java/com/commsen/liferay/examples/portlet/servicebuilder/JSPPortlet.java</code> and add this method</p> <p><code class="blockWhite"> &nbsp;&nbsp;&nbsp; @ProcessAction(name = Constants.ADD)<br /> &nbsp;&nbsp; &nbsp;public void addPlayer(ActionRequest actionRequest, ActionResponse actionResponse) throws PortletException, IOException {<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;String name = ParamUtil.getString(actionRequest, &quot;name&quot;);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;boolean active = ParamUtil.getBoolean(actionRequest, &quot;active&quot;);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int score = ParamUtil.getInteger(actionRequest, &quot;score&quot;);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;String description = ParamUtil.getString(actionRequest, &quot;description&quot;);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int year = ParamUtil.getInteger(actionRequest, &quot;birthday_year&quot;);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int month = ParamUtil.getInteger(actionRequest, &quot;birthday_month&quot;);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int day = ParamUtil.getInteger(actionRequest, &quot;birthday_day&quot;);<br /> <br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Calendar calendar = Calendar.getInstance();<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;calendar.set(Calendar.YEAR, year);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;calendar.set(Calendar.MONTH, month);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;calendar.set(Calendar.DAY_OF_MONTH, day);<br /> <br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;try {<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;PlayerLocalServiceUtil.addPlayer(name, active, score, calendar.getTime(), description);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;} catch (Exception e) {<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;throw new PortletException(&quot;Failed to add player&quot;, e);<br /> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br /> &nbsp;&nbsp; &nbsp;}</code></p> <p>This method handles adding players to database by calling the method we created earliar. To complete the portlet we only need the JSP page which displays the list and render HTML form to add users. The page source code is available <a href="http://github.com/azzazzel/liferay-maven-sdk/blob/master/examples/service-builder-portlet/src/main/webapp/view.jsp">here</a>.</p> <p>That's all! We can now create WAR file (<code>mvn package</code>) deploy it and add some players to our database!&nbsp; For more details have a look at portlet's source code available in <a href="http://github.com/azzazzel/liferay-maven-sdk/tree/master/examples/service-builder-portlet">examples/service-builder-portlet</a>      folder of <code>liferay-maven-sdk</code>.</p>]]>
    </content>
</entry>

<entry>
    <title>Run GWT application in &quot;hosted mode&quot; from maven</title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2009/04/run-gwt-application-in-hosted-mode-from-maven.html" />
    <id>tag:milen.commsen.com,2009://1.8</id>

    <published>2009-04-26T10:39:35Z</published>
    <updated>2009-04-26T11:59:14Z</updated>

    <summary>It seems to get more and more cloudy in the IT world these days . It&apos;s a matter of time before the rain (of applications) starts. When this happen one will need the proper tools, to be able to add...</summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="tips and tricks" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="gwt" label="GWT" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="maven" label="Maven" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="plugin" label="plug-in" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p>It seems to get more and more <a href="http://www.infoworld.com/d/cloud-computing/nick-carr-many-ways-cloud-computing-will-disrupt-it-798">cloudy</a> in the IT world  these days . It's a matter of time before the rain (of applications) starts. When this happen one will need the proper tools, to be able to add his/hers own few drops.</p><p>So I though it's about time to start experimenting with <a href="http://code.google.com/webtoolkit/">Google Web Toolkit</a>. What I like the most about GWT is it's &quot;hosted mode&quot;. The fact that Java code changes reflect the GUI right away and one don't have to wait for generate, compile, build, deploy, ... steps to complete is really speeding up the development process.<br /> <br /> Since 99% of my projects use <a href="http://maven.apache.org/">Maven</a> the first thing to look for (after reading GWT tutorials) was a GWT maven plug-in. No surprise here - there is one (<a href="http://mojo.codehaus.org/gwt-maven-plugin">http://mojo.codehaus.org/gwt-maven-plugin</a>). The <a href="http://code.google.com/webtoolkit/gettingstarted.html">GWT docs</a> and <a href="http://mojo.codehaus.org/gwt-maven-plugin/1.1-SNAPSHOT/">gwt-maven-plugin docs</a> gives a lot of information how to create and build GWT applications.&nbsp; Unfortunately the released version of&nbsp; gwt-maven-plugin (1.0 at the time of writing) does not support hosted mode.</p>]]>
        <![CDATA[<p>The solution is to use the snapshot repository<br /> <code class="blockWhite"> &nbsp;&nbsp;&nbsp; &lt;pluginRepository&gt;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;id&gt;mojo-snapshots&lt;/id&gt;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;url&gt;http://snapshots.repository.codehaus.org&lt;/url&gt;<br /> &nbsp;&nbsp;&nbsp; &lt;/pluginRepository&gt;<br /> </code>  and the 1.1-SNAPSHOT version :<br /> <code class="blockWhite"> &nbsp;&nbsp;&nbsp; &lt;pluginManagement&gt;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;plugins&gt;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;plugin&gt;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;artifactId&gt;gwt-maven-plugin&lt;/artifactId&gt;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;version&gt;1.1-SNAPSHOT&lt;/version&gt;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;/plugin&gt;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;/plugins&gt;<br /> &nbsp;&nbsp;&nbsp; &lt;/pluginManagement&gt;<br /> </code> If the application only contains client code it can be then run in &quot;hosted mode&quot; by simply typing <code>mvn gwt:run</code>. The thing docs don't mention (again, at the time of writing this) is that &quot;mvn gwt:run&quot; will not build the server side of the application. Unless you do this yourself GWT-RPC call will not work. So there are three possible ways:</p> <ol>     <li>let the Eclipse (or whatever IDE you use) build the classes in <code>${basedir}/src/main/webapp/WEB-INF/classes</code><br />     <br />     I personally don't like this approach. It does not solve the problem if you have multiple modules in maven as the dependent libraries will be still missing. Also you have to add some rules to your SCM system to ignore generated classes. <br />     <br />     &nbsp;</li>     <li>do <code>mvn compile war:inplace gwt:run</code><br />     <br />     This will create extracted version of the war in &quot;${basedir}/src/main/webapp&quot;. It will solve the problem even for multiple modules setup but you still will have to deal with the SCM ignore rules.<br />     &nbsp;</li>     <li>do <code>mvn compile war:exploded gwt:run</code> <br />     <br />     This will create extracted version of the WAR in a specified directory (by default it is <code>${project.build.directory}/${project.build.finalName}</code> as stated in <a href="http://maven.apache.org/plugins/maven-war-plugin/exploded-mojo.html#webappDirectory">Maven WAR Plugin docs</a>). In order for this to work the <code>gwt-maven-plugin</code> needs to be told where the exploded war is by adding this to it's configuration in pom.xml : <br />     <code class="blockWhite">      &nbsp;&nbsp;&nbsp; &lt;hostedWebapp&gt;<br />     &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ${project.build.directory}/${project.build.finalName}<br />     &nbsp;&nbsp;&nbsp; &lt;/hostedWebapp&gt;<br />     </code>      This IMHO is the best approach. Solves the problem even in case of multiple modules and your SCM managed folders remain clean.</li> </ol>]]>
    </content>
</entry>

<entry>
    <title>ATG session tracking cookies and subdomains.</title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2009/01/atg-session-tracking-cookies-and-subdomains.html" />
    <id>tag:milen.commsen.com,2009://1.7</id>

    <published>2009-01-29T13:50:58Z</published>
    <updated>2009-01-31T16:21:08Z</updated>

    <summary>If an ATG based web application is available under few subdomains (domain.com, www.domain.com, shop.domain.com) keeping track of session cookies across subdomains may be a challenge. Session tracking cookies (like jsessionid) usually do not have domain property set, which means they...</summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="ATG" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="tips and tricks" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="atg" label="ATG" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="cookie" label="cookie" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="jboss" label="JBoss" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="tomcat" label="Tomcat" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="valve" label="valve" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p style="text-align: justify;">If an ATG based web application is available under few subdomains (domain.com, www.domain.com, shop.domain.com) keeping track of session cookies across subdomains may be a challenge. Session tracking cookies (like jsessionid) usually do not have domain property set, which means they are sent back to exactly the same host they came from. So if visitors switch to another subdomain while navigating through the application they would most likely end up having a new session. Depending on what information session holds, the number of visitors and how many simultaneous sessions the server can handle, this may or may not be a problem.</p>]]>
        <![CDATA[<p style="text-align: justify;">The best solution is obviously not to let your visitors change the domain while browsing you site (for example by using relative links only). But if your application occupies the whole domain it may be easier and safer to set the domain property for all relevant cookies. Depending on what ATG modules you use, a number of cookies used for session tracking may vary.</p> <p style="text-align: justify;">Here is how this can be done on ATG 2006.3 running on JBoss 4.0.3.Since JBoss uses Tomcat as web container, we will add a valve to server.xml in which we'll replace a original <code>HttpServeltResponse</code> with custom wrapper. Actually this is a slightly modified version of the solution described here <a href="https://jira.jboss.org/jira/browse/JBWEB-107">https://jira.jboss.org/jira/browse/JBWEB-107</a>. So here is our valve:</p> <p><code class="block">  public class CookieRewriteValve extends ValveBase { <br /> &nbsp;&nbsp;&nbsp; private String domain = null;<br /> &nbsp;&nbsp;&nbsp; private String cookieNames = null;<br /> &nbsp;&nbsp;&nbsp; private Set cookiesToModify = null;<br /> <br /> &nbsp;&nbsp;&nbsp; public void postRegister(Boolean registrationDone) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (registrationDone.booleanValue()) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (cookieNames != null &amp;&amp; cookieNames.trim().length() &gt; 0) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; cookiesToModify = new HashSet(Arrays.asList(cookieNames.toUpperCase().split(&quot;,\\s*&quot;)));<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br /> &nbsp;&nbsp;&nbsp; }<br /> <br /> &nbsp;&nbsp;&nbsp; public void invoke(Request request, Response response) throws IOException, ServletException {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; CookieModifier.createThreadInstance(cookiesToModify, path, domain, secure, maxAge);<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; response = new CookieRewriteResponseWrapper(response);<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; request.setResponse(response);<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; getNext().invoke(request, response);<br /> &nbsp;&nbsp;&nbsp; }<br /> <br /> &nbsp;&nbsp;&nbsp; public String getDomain() {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return domain;<br /> &nbsp;&nbsp;&nbsp; }<br /> <br /> &nbsp;&nbsp;&nbsp; public void setDomain(String domain) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.domain = domain;<br /> &nbsp;&nbsp;&nbsp; }<br /> <br /> &nbsp;&nbsp;&nbsp; public String getCookieNames() {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return cookieNames;<br /> &nbsp;&nbsp;&nbsp; }<br /> <br /> &nbsp;&nbsp;&nbsp; public void setCookieNames(String cookieNames) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.cookieNames = cookieNames;<br /> &nbsp;&nbsp;&nbsp; }<br /> }   </code></p> <p>It has to be registered in &quot;Engine&quot; section of <code>&lt;PATH_TO_JBOSS&gt;/server/&lt;SERVER_NAME&gt;/deploy/jbossweb-tomcat55.sar/server.xml</code> like this</p> <p><code class="blockWhite"> &lt;Valve domain=&quot;.domain.com&quot; cookieNames=&quot;jsessionid, ATG_SESSION_ID&quot; className=&quot;cookie.rewrite.example.CookieRewriteValve&quot;/&gt;</code></p> <p style="text-align: justify;">At startup engine initializes declared valves and calls <code>postRegister</code> method on each. This is where we read, parse and store arguments. Later on the <code>invoke</code> method is called by the engine on every incoming request. This is where we create a new thread local instance of <code>CookieModifier</code> and wrap the original <code>Response</code> in <code>CookieRewriteResponseWrapper</code>. This wrapper is constructed by passing a reference to the original <code>Response</code>:</p> <p><code class="block"> public class CookieRewriteResponseWrapper extends org.apache.catalina.connector.Response<br /> <br /> &nbsp;&nbsp;&nbsp; protected Response res;<br /> <br /> &nbsp;&nbsp;&nbsp; public CookieRewriteResponseWrapper(Response res) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.res = res;<br /> &nbsp;&nbsp;&nbsp; }<br /> &nbsp;&nbsp;&nbsp; ....<br /> </code></p> <p style="text-align: justify;">It also overwrites EVERY public method from <code>org.apache.catalina.connector.Response</code> like this:</p> <p><code class="block"> &nbsp;&nbsp;&nbsp; public returnType methodName (parameters...) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return res.methodName (parameters...)<br /> &nbsp;&nbsp;&nbsp; }<br /> </code></p> <p style="text-align: justify;">except the <code>addCookie(Cookie cookie)</code> method which tries to get an instance of <code>CookieModifier</code> and use it to modify the cookie before it delegates the request to the original <code>Response</code> object:</p> <p><code class="block"> &nbsp;&nbsp;&nbsp; public void addCookie(Cookie cookie) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; CookieModifier cookieModifier = CookieModifier.getInstance();<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (cookieModifier != null) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; cookieModifier.modify(cookie);<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; res.addCookie(cookie);<br /> &nbsp;&nbsp;&nbsp; }<br /> </code></p> <p style="text-align: justify;">Finally here is how <code>CookieModifier</code> looks like:</p> <p><code class="block"> public class CookieModifier {<br /> <br /> &nbsp;&nbsp;&nbsp; protected String cookieDomain;<br /> &nbsp;&nbsp;&nbsp; protected Set cookiesToModify = null;<br /> &nbsp;&nbsp;&nbsp; protected boolean modifyAll = false;<br /> <br /> &nbsp;&nbsp;&nbsp; private static ThreadLocal threadInstance = new ThreadLocal();<br /> <br /> &nbsp;&nbsp;&nbsp; private CookieModifier(Set cookiesToModify, String cookieDomain) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.cookieDomain = cookieDomain;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.cookiesToModify = cookiesToModify;<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (cookiesToModify == null || cookiesToModify.isEmpty()) modifyAll = true;<br /> &nbsp;&nbsp;&nbsp; }<br /> <br /> &nbsp;&nbsp;&nbsp; public static void createThreadInstance(Set cookiesToModify, String cookieDomain) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; threadInstance.set(new CookieModifier(cookiesToModify, cookieDomain));<br /> &nbsp;&nbsp;&nbsp; }<br /> <br /> &nbsp;&nbsp;&nbsp; public static CookieModifier getInstance() {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return (CookieModifier) threadInstance.get();<br /> &nbsp;&nbsp;&nbsp; }<br /> <br /> &nbsp;&nbsp;&nbsp; public void modify(Cookie cookie) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (modifyAll || cookiesToModify.contains(cookie.getName()))<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (cookieDomain != null) cookie.setDomain(cookieDomain);<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br /> &nbsp;&nbsp;&nbsp; }<br /> <br /> }<br /> </code></p> <p style="text-align: justify;">Note the static methods used to store a new instance of this class in current thread.&nbsp; It will become clear why this is important later.</p> <p style="text-align: justify;">So all we have to do now is to pack these classes in a jar file, place it in <code>&lt;PATH_TO_JBOSS&gt;/server/&lt;SERVER_NAME&gt;/lib</code> and add the valve line to <code>server.xml</code>. But if you try to run this, you will notice that while it works for &quot;jsessionid&quot; it does not for &quot;ATG_SESSION_ID&quot;. This is because ATG itself wraps the <code>Response</code> object in DynamoHttpServletResponse which apparently does not make use of the original <code>addCookie</code> method! Fortunately though, ATG allows user to have custom implementations of <code>DynamoHttpServletResponse</code>. To tell ATG we have a custom implementation we need to add the following configuration to <code>/atg/dynamo/servlet/dafpipeline/DynamoHandler</code> component:</p> <p><code class="blockWhite"> responseClass=cookie.rewrite.example.DynamoHttpServletResponseWrapper </code></p> <p style="text-align: justify;">Now when ATG needs to create <code>DynamoHttpServletResponse</code> it will actually create an instance of <code>DynamoHttpServletResponseWrapper</code>. But how can our implementation obtain access to appropriately configured instance of <code>CookieModifier</code>? It can do so because our <code>CookieRewriteValve</code>, executed earlier in the same thread, placed an instance of <code>CookieModifier</code> in thread local variable. Here is how <code>DynamoHttpServletResponseWrapper</code> looks like:</p> <p><code class="block"> public class DynamoHttpServletResponseWrapper extends DynamoHttpServletResponse {<br /> <br /> &nbsp;&nbsp;&nbsp; public void addCookie(Cookie cookie) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; CookieModifier cookieModifier = CookieModifier.getInstance();<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (cookieModifier != null) {<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; cookieModifier.modify(cookie);<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br /> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super.addCookie(cookie);<br /> &nbsp;&nbsp;&nbsp; }<br /> } </code></p> <p>That's it.&nbsp; Once we add this class to the jar created earlier and restart JBoss it should work like a charm ;)</p>]]>
    </content>
</entry>

<entry>
    <title>Eclipse error message </title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2009/01/eclipse-error-message.html" />
    <id>tag:milen.commsen.com,2009://1.6</id>

    <published>2009-01-23T20:49:07Z</published>
    <updated>2009-01-29T17:14:13Z</updated>

    <summary>Don&apos;t know why but every once in a while I find myself reading some article, tutorial or case study about error handling and creating useful error messages. No doubt it&apos;s very important in what way you serve the bad news...</summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="Miscellaneous" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="eclipse" label="eclipse" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="error" label="error" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="message" label="message" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p style="text-align: justify;">Don't know why but every once in a while I find myself reading some article, tutorial or case study about error handling and creating useful error messages. No doubt it's very important in what way you serve the bad news (which error messages actually are). But on the other hand, how much time one can spend on error handling and constructing good error messages?</p>]]>
        <![CDATA[<p style="text-align: justify;">And just when I was starting to believe it's not worth the effort, this error message appeared on my screen:<br />&nbsp;<img width="620" height="270" style="margin: 0pt auto 20px; text-align: center; display: block;" class="mt-image-center" src="http://milen.commsen.com/Screenshot.png" alt="eclipse-error.png" /></p><p style="text-align: justify;">I don't want my users to feel the way I felt staring at it! Lesson learned, good error message is worth the effort!</p>]]>
    </content>
</entry>

<entry>
    <title>I just finished installing Movable Type</title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2009/01/i-just-finished-installing-movable-type-4.html" />
    <id>tag:milen.commsen.com,2009://1.1</id>

    <published>2009-01-01T22:42:34Z</published>
    <updated>2009-01-29T17:15:36Z</updated>

    <summary>Welcome to my new blog powered by Movable Type. Finally got it up and running but it took me a few hours. Cutting the long story short, here is a note of what problems I had and how they were...</summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="tips and tricks" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="cpan" label="cpan" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="install" label="install" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="modules" label="modules" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="movabletype" label="movabletype" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="problem" label="problem" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="ubuntu" label="ubuntu" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p style="text-align: justify;">Welcome to my new blog powered by Movable Type. Finally got it up and running but it took me a few hours. Cutting the long story short, here is a note of what problems I had and how they were solved.</p>]]>
        <![CDATA[<p style="text-align: justify;">Since there was no Movable Type package for Ubuntu 8.04 I downloaded version 4.23 from <a href="http://www.movabletype.org/">http://www.movabletype.org/</a> and unpacked it locally. Then I created&nbsp; <small><code>/usr/lib/cgi-bin/movabletype/ </code></small>folder, copied in there <small><code>*.cgi</code></small> files from <small><code>/path/to/movabletype/</code></small> and made symlinks to these folders <br /> <br /> <code class="block"> /usr/lib/cgi-bin/movabletype/default_templates -&gt; /path/to/movabletype/default_templates <br />/usr/lib/cgi-bin/movabletype/lib&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; /path/to/movabletype/lib<br />/usr/lib/cgi-bin/movabletype/plugins&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; /path/to/movabletype/plugins <br />/usr/lib/cgi-bin/movabletype/tmpl&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; /path/to/movabletype/tmpl </code> <br /> I already had Apache configured so it was time to start the installation  wizard. But after typing <small><code>http://milen.commsen.com/cgi-bin/movabletype/mt.cgi</code></small> in Firefox I got: <br /> <br /> <code class="blockWhite"> Got an error: Base class package &quot;Class::Accessor::Fast&quot; is empty.<br />&nbsp;&nbsp;&nbsp; (Perhaps you need to 'use' the module which defines that package first.) </code> <br /> Took me a while to figure out the name of the package containing this Perl module but once it was discovered  <br /> <br /> <code class="block">apt-get install libclass-accessor-perl</code> <br /> solved this issue and made room for t the next one:  <br /> <br /> <code class="blockWhite"> Got an error: Base class package &quot;Data::ObjectDriver::BaseObject&quot; is empty.<br /> &nbsp;&nbsp;&nbsp; (Perhaps you need to 'use' the module which defines that package first.) </code> <br /> The same problem, just different module, except this time there was no Ubuntu package for it. So I had to install it from <a href="http://www.cpan.org/">CPAN</a>, which was quite easy to do using <small><code>cpan</code></small> command. <br /> <br /> <code class="block"> bash$ cpan<br /><br /> cpan shell -- CPAN exploration and modules installation (v1.7602)<br /> ReadLine support available (try 'install Bundle::CPAN')<br /><br /> cpan&gt; </code>  <br /> I then typed  <br /> <br /> <code class="block"> cpan&gt; install Data::ObjectDriver<br /> </code> <br /> and after a while got this message: <br /> <br /> <code class="block"> *** Checking for Perl dependencies...<br />[Core Features]<br />- Test::Exception&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...missing.<br />- DBI&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...loaded. (1.601)<br />- Class::Accessor::Fast&nbsp;&nbsp;&nbsp; ...loaded. (0.31)<br />- Class::Data::Inheritable ...missing.<br />- Class::Trigger&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...missing.<br />- List::Util&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...loaded. (1.18)<br />==&gt; Auto-install the 3 mandatory module(s) from CPAN? [y]<br /> </code> <br /> Since missing modules depend on other missing modules I just let <small><code>cpan</code></small> handle dependencies and few minutes later problem was solved and now I had:  <br /> <br /> <code class="blockWhite"> Got an error: mutiple trigger registration in one add_trigger() call is deprecated. </code> <br /> Thanks to Google I found the solution in another blog entry called &quot;<a href="http://www.glorat.net/2008/11/movable-type-with-classtrigger-012.html">Movable Type with Class::Trigger 0.12</a>&quot; and after changing <br /> <br /> <code class="block"> MT::Placement-&gt;add_trigger(<br /> &nbsp;&nbsp;&nbsp;&nbsp; post_save&nbsp;&nbsp; =&gt; \&amp;flush_category_cache,<br /> &nbsp;&nbsp;&nbsp;&nbsp; post_remove =&gt; \&amp;flush_category_cache<br /> );<br /> </code> <br /> to <br /> <br /> <code class="block"> MT::Placement-&gt;add_trigger(<br /> &nbsp;&nbsp;&nbsp;&nbsp; post_save&nbsp;&nbsp; =&gt; \&amp;flush_category_cache<br /> );<br /> MT::Placement-&gt;add_trigger(<br /> &nbsp;&nbsp;&nbsp;&nbsp; post_remove =&gt; \&amp;flush_category_cache<br /> );<br /> </code> <br /> in <small><code>MT/Entry.pm</code></small> I finally got installation wizard running. At some point, after checking for required modules, the wizard complained about missing <small><code>Image::Size</code></small> and I had had to do  <br /> <br /> <code class="block"> apt-get install libimage-size-perl. </code> <br />  There was also warning about missing optional modules so I also installed: <br /> <br /> <code class="block"> libmail-sendmail-perl<br /> libsoap-lite-perl<br /> libxml-atom-perl<br /> perlmagick<br /> libgd-gd2-noxpm-perl </code> <br /> Rest of configuration process went without problems but after completing the wizard I got: <br /> <br /> <code class="blockWhite"> Got an error: Can't locate YAML/Tiny.pm in @INC ... </code> <br /> Another Perl module missing? Looks like wizard didn't check for this one. So <br /> <br /> <code class="block">apt-get install libyaml-tiny-perl</code> <br /> added it but then another one showed up: <br /> <br /> <code class="blockWhite">Got an error: Can't locate JSON.pm in @INC</code> <br /> This was fixed by  <br /> <br /> <code class="block">apt-get install libjson-perl </code> <br /> and finally there was &quot;Create Your Account&quot; screen.&nbsp; Creating an account and adding a blog went without problems and I was able to move my old posts from Blogger and add this one. </p>]]>
    </content>
</entry>

<entry>
    <title>Simple Java program to merge Excel survey results</title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2008/12/simple-java-program-to-merge-excel-survey-results.html" />
    <id>tag:milen.commsen.com,2008://1.4</id>

    <published>2008-12-29T13:13:20Z</published>
    <updated>2009-07-11T08:39:55Z</updated>

    <summary>A friend of mine recently asked me about merging survey results, which reminded me I had similar problem about an year ago and have written a peace of code to solve it. It&apos;s not a framework or user friendly application...</summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="Software" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="excel" label="excel" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="java" label="java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="merge" label="merge" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="poi" label="POI" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="poll" label="poll" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="survey" label="survey" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="template" label="template" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="xls" label="xls" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p style="text-align: justify;">A friend of mine recently asked me about merging survey results, which reminded me I had similar problem about an year ago and have written a peace of code to solve it. It's not a framework or user friendly application and it's not well documented. It was written in a couple of hours to solve particular problem, but in case anyone is interested here is&nbsp; so called <a href="http://milen.commsen.com/files/SpreadSurvey.zip">SpreadSurvey</a>.</p>]]>
        <![CDATA[<p style="text-align: justify;">&nbsp;</p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img width="287" height="165" alt="survey.png" src="http://milen.commsen.com/img/ss/survey.png" class="mt-image-left" style="margin: 0pt 20px 20px 0pt; float: left;" /></span><p>The problem I was facing back then, was how to ask about 20 questions to about 50 people and get their answers into single excel file. There is plenty of tools and services out there for managing polls and surveys, but I was not allowed to place company related data outside company's network.&nbsp; &nbsp; <br />So I prepared a survey as simple form in a excel file (unfortunately can not show the actual one here, but it was much like this sample one) and sent it to everyone in my target group, kindly asking them to fill it in and send it back. So I got back about 50 files containing filled forms. The question was how to merge them into single file? <br />Since I'm a Java geek and I had been using Apache POI already and those files had all the same structure, the answer was obvious. A simple Java program could iterate over those files extract any useful data and merge it into a single file. This is how an eclipse quickie called&nbsp; SpreadSurvey was born. </p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img width="229" height="111" alt="survey-template-1.png" src="http://milen.commsen.com/img/ss/survey-template-1.png" class="mt-image-left" style="margin: 0pt 20px 20px 0pt; float: left;" /></span><p>Besides the files containing the answers it needs a template for the output file.Template is nothing more than another excel file containing some special tags. <i><b>{ss}:this</b></i> extract data from the cell having exactly the same row and column as the one in template file. <i><b>{ss}:cell(C:R)</b></i> extracts data from cell at column C and row R.The result file is produced by repeating (horizontally or vertically) the template area containing special tags for each result file. For example if we have a simple template and 3 files containing answers to the sample survey as shown in above images, then this: <br /> <br /> <code class="block"> $ java com.commsen.ss.MergeResults -t template.xls -r result.xls -d X /tmp/results/* </code> <br /> will print  <br /> <br /> <code class="block"> Found expression at 1:2 <br />Found expression at 1:3 <br />Found expression at 1:4 <br />Found expression at 1:5 <br />Template expresions found in area 1:2 - 1:5 <br />processing file: /tmp/results/survey_result1.xls <br />processing file: /tmp/results/survey_result2.xls <br />processing file: /tmp/results/survey_result3.xls </code> <br /><br /> and will generate file<small><code>/tmp/result.xls</code></small> which looks like this: </p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img width="382" height="122" alt="survey-result-1.png" src="http://milen.commsen.com/img/ss/survey-result-1.png" class="mt-image-center" style="margin: 0pt auto 20px; text-align: center; display: block;" /></span><p>Alternatively the same data can be organized in columns instead of rows. Just need to provide different template  </p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img width="324" height="90" alt="survey-template-2.png" src="http://milen.commsen.com/img/ss/survey-template-2.png" class="mt-image-center" style="margin: 0pt auto 20px; text-align: center; display: block;" /></span><p>and tell SpreadSurvey to repeat vertically  <br /> <br /> <code class="block"> $ java com.commsen.ss.MergeResults -t template2.xls -r result2.xls -d <b>Y</b> /tmp/results/*<br /> Found expression at 1:2<br /> Found expression at 2:2<br /> Found expression at 3:2<br /> Found expression at 4:2<br /> Template expresions found in area 1:2 - 4:2<br /> processing file: /tmp/results/survey_result1.xls<br /> processing file: /tmp/results/survey_result2.xls<br /> processing file: /tmp/results/survey_result3.xls<br /> </code>  <br /> and  file <small><code>/tmp/result2.xls</code></small> will look like this:<br />&nbsp;</p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img width="322" height="103" alt="survey-result-2.png" src="http://milen.commsen.com/img/ss/survey-result-2.png" class="mt-image-center" style="margin: 0pt auto 20px; text-align: center; display: block;" /></span><p>So as I said earlier there is no rocket science here. It' s just simple tool I wrote some time ago, that turned out to be useful for someone else. It was never meant to be released or further developed so there is no JavaDoc and the code is not documented either. Only this help screen shows how to use it: <br /> <br /> <code class="block"> $ java com.commsen.ss.MergeResults --help<br />Usage:&nbsp; MergeResults [OPTION...] FILE...<br /><br /> Options:<br />&nbsp; -t, --template-file=&lt;filename&gt;&nbsp;&nbsp; Specify the name of the template file.<br />&nbsp; -r, --result-file=&lt;filename&gt;&nbsp;&nbsp;&nbsp;&nbsp; Specify the name of the result file.<br />&nbsp; -O, --overwrite&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Add to overwrite result file if exists.<br />&nbsp; -d, --direction=&lt;X|Y&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Specify whether data is added in columns or <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rows (default is X)<br />&nbsp; -S, --skip-broken&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Add to skip broken files<br /><br />Help options:<br />&nbsp; -?, --help&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; show this help message<br />&nbsp; --usage&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; show brief usage message </code> <br /> <br />Feel free to use it and/or modify it as needed. Don't forget to send me feedback if you do so ;) &nbsp; <br />The package contains source code as well as unmodified versions of used libraries :&nbsp; <a href="http://poi.apache.org/">POI</a>, <a href="http://commons.apache.org/io/">commons-io</a> and <a href="http://te-code.sourceforge.net/">te-common</a>. <b><i><font style="font-size: 0.8em;">(Please check appropriate sites regarding licensing and terms of usage)</font></i></b>.</p><p>&nbsp;</p>]]>
    </content>
</entry>

<entry>
    <title>Tagging the latest version of previously tagged files in CVS </title>
    <link rel="alternate" type="text/html" href="http://milen.commsen.com/2006/02/tagging-the-latest-version-of-previously-tagged-files-in-cvs.html" />
    <id>tag:milen.commsen.com,2006://1.3</id>

    <published>2006-02-10T02:06:44Z</published>
    <updated>2009-01-29T17:17:36Z</updated>

    <summary>Some time ago a set of files ware committed to CVS repository and tagged (lets say with TAG1) . These files have changed a few times since then. Today I needed to tag the latest versions of all files that...</summary>
    <author>
        <name>Milen Dyankov</name>
        
    </author>
    
        <category term="tips and tricks" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="cvs" label="CVS" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="linux" label="linux" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="tag" label="tag" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://milen.commsen.com/">
        <![CDATA[<p style="text-align: justify;">Some time ago a set of files ware committed to CVS repository and tagged (lets say with TAG1) . These files have changed a few times since then. Today I needed to tag the latest versions of all files that have ever been tagged TAG1 with TAG2.</p>]]>
        <![CDATA[<p><br />This</p><div style="border: 1px solid rgb(136, 136, 136); margin: 5px; padding: 2px; background-color: rgb(221, 221, 221); font-weight: bold; font-family: courier new; font-size: 10px;">cvs -Q log -R -S -rTAG1 .</div><p><br />  gave me the files I was looking for but with <strong>/cvsroot/</strong> in front and <strong>,v </strong> at the end</p><div style="border: 1px solid rgb(136, 136, 136); margin: 5px; padding: 2px; background-color: rgb(221, 221, 221); font-family: courier new; font-size: 10px;">/cvsroot/path/to/file1,v<br />/cvsroot/path/to/file2,v<br />...</div><p><br />  so I had to remove it</p><div style="border: 1px solid rgb(136, 136, 136); margin: 5px; padding: 2px; background-color: rgb(221, 221, 221); font-weight: bold; font-family: courier new; font-size: 10px;">cvs -Q log -R -S -rTAG1 . | sed s#/cvsroot/## | sed s#,v#\#</div><p><br />  now I could actually retag these files</p><div style="border: 1px solid rgb(136, 136, 136); margin: 5px; padding: 2px; background-color: rgb(221, 221, 221); font-weight: bold; font-family: courier new; font-size: 10px;">cvs -q tag TAG2 `cvs -Q log -R -S -rTAG1 . | sed s#/cvsroot/## | sed s#,v#\#`</div><p><br />  It did the job but I think it's quite ugly way of doing such a &quot;simple&quot; operation.<br />  There must be a another  (simpler | non *nix specific) way!</p>]]>
    </content>
</entry>

</feed>
