tag:blogger.com,1999:blog-155367592024-03-14T12:48:51.375+05:30For those who came in late...abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.comBlogger22125tag:blogger.com,1999:blog-15536759.post-44303735891712951962014-08-16T18:03:00.000+05:302014-08-16T18:03:27.038+05:30<h2>This blog has moved to <a href="http://abhi.sanoujam.com">http://abhi.sanoujam.com</a> permanently!</h2>
<p/>
<p/>
<p/>
Please update your feeds :)
abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com0tag:blogger.com,1999:blog-15536759.post-494057728254996632010-05-14T18:29:00.003+05:302010-05-14T18:53:44.287+05:30Superset!During my first year in college, I still remember when we were asked to write a program to generate a super-set of an input set (in Data Structures course, I think). A super-set of a given set S was defined as a set containing all possible subsets of the set S. So for example for the set S = {A, B, C} the superset would consist of the following sets:<br />{}<br />{A}<br />{B}<br />{C}<br />{A, B}<br />{A, C}<br />{B, C}<br />{A, B, C}<br /><br />That time, which is like 9 years ago, i came up with some weird theory (i don't remember the details at all), and came up with some , i think, 400-500 loc C program to generate the superset. I remember sitting up all night and coming up with the theory... I had manually generated supersets of some example sets and was sitting staring at the generated supersets. My theory was really weird and had something to do about the behavior of how the elements were ordered in the generated sub-sets. And the program was perfectly correct and I was the one of the few who got full marks for that assigment :)<br />Wish I could see that code now...<br /><br /><br />Coming back to present, I came upon the same requirement -- to generate a superset of a set. Was playing with some code and wanted to generate all possible input sets from a set of valid inputs - a superset - for my testing purposes. And now, being a java programmer, I came up with this simple function to generate the superset in java:<br /><br /><pre name="code" class="java:expand"><br />public static <E> Set<Set<E>> superset(Set<E> set) {<br /> List<E> setAsList = new ArrayList<E>(set);<br /> Set<Set<E>> rv = new HashSet<Set<E>>();<br /> int maxNumber = (int) Math.pow(2, set.size());<br /> for (int number = 0; number < maxNumber; number++) {<br /> char[] bins = Integer.toBinaryString(number).toCharArray();<br /> Set<E> subset = new HashSet<E>();<br /> for (int i = bins.length - 1; i >= 0; i--) {<br /> if ((bins[i] == '1')) {<br /> subset.add(setAsList.get(bins.length - 1 - i));<br /> }<br /> }<br /> rv.add(subset);<br /> }<br /> return rv;<br />}<br /></pre><br /><br /><br />Now that's hardly 15 loc, and it supports generics too, the 400-500 loc C code supported generating superset of set of int's only :)<br />I guess I've improved myself a bit as a coder :)abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com21tag:blogger.com,1999:blog-15536759.post-38840737111861902082010-02-18T00:41:00.007+05:302010-02-18T02:39:20.882+05:30CyclicBarrier on multiple VM'sWell, if you don't know what CyclicBarrier is, its probably best for you to skip this post and do something else :) ...<br /><br />Just kidding... but if you really don't know what CyclicBarrier is, you should go read <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/CyclicBarrier.html">this</a>, spend some time googling and come back, its a useful class in case you are writing concurrent-multi-threaded programs in java.<br /><br />One popular use-case of a CyclicBarrier is in computing tasks which can be broken down into independent sub-tasks. You create a bunch of threads and let each thread compute the result of the sub-tasks, wait for all threads to complete and combine the results from each thread/sub-task to get the final result.<br /><br />All of this sounds cool. Now when you want to scale out, i.e. add more VM's/boxes/machines to get more throughput, it sounds kind of difficult.... how to distribute the sub-tasks, how to wait for all of the threads to complete their sub-tasks in different vm's/nodes etc.<br />If you've heard about Terracotta or used it before, you'd say its not really tough... u can drop in tc-config, add roots, make CyclicBarrier roots, start with dso-java etc.... <br />For those who don't know terracotta, its kind of intimidating thinking of learning terracotta concept -- the roots, tc-config, bootjar and stuff.<br /><br />Now with the latest Terracotta release that's coming up... its really easy. The new release supports a clustered Barrier, which is similar with CyclicBarrier ... and clustered on top of that. Which means you can have threads wait for each other on different vm's/nodes etc. And all you need to do is drop a jar and an API call to create the barrier.<br /><br />To demonstrate it, here's a sample app and how to run it...<br />The app uses ehcache clustered with terracotta to share the results from each node. Creating clustered ehcache with terracotta is also dead easy.. its done programmatically here in the app, but you can also do it easily using ehcache.xml<br /><br />Download the latest Terracotta kit (or build it from source) and start tc server <br /><pre name="code" class="console"><br />$ cd $TC_INSTALL_DIR/bin<br />$ ./start-tc-server.sh<br /></pre><br /><br />Download the source + libs of the sample app from <a href="http://abhi-sanoujam-blogspot-posts.googlecode.com/svn/trunk/ClusteredCyclicBarrier/">here</a>. <br />Open up 3 terminals (the program is hardcoded to expect 3 nodes), and in each terminal, do the following:<br /><pre name="code" class="console"><br /><br />$ cd ClusteredCyclicBarrier<br />$ ant run<br /><br /></pre> <br /><br />You should see some kind of output like this:<br /><pre name="code" class="console"><br />$ ant run<br />Buildfile: build.xml<br /><br />init:<br /><br />clean:<br /><br />compile:<br /> [javac] Compiling 1 source file to /Users/asingh/workspace/projects/ClusteredCyclicBarrier/target/classes<br /><br />run:<br /> [java] 2010-02-18 01:59:42,355 INFO - Terracotta 3.3.0-SNAPSHOT, as of 20100217-120224 (Revision 14573 by asingh@paladin.local from trunk)<br /> [java] 2010-02-18 01:59:42,871 INFO - Configuration loaded from the server at 'localhost:9510'.<br /> [java] 2010-02-18 01:59:42,960 INFO - Configuration loaded from the file at '/var/folders/3R/3Rx0og6cF-KjD-C7zWtiD++++TM/-Tmp-/tc-config5414381556581263394.xml'.<br /> [java] <br /> [java] WARN: The log directory, '/Users/asingh/terracotta/client-logs', is already in use by another Terracotta process. Logging will proceed to the console only.<br /> [java] <br /> [java] 2010-02-18 01:59:44,464 INFO - Connection successfully established to server at 10.0.0.3:9510<br /> [java] 2010-02-18 01:59:45,801 INFO - Terracotta 3.3.0-SNAPSHOT, as of 20100217-120224 (Revision 14573 by asingh@paladin.local from trunk)<br /> [java] 2010-02-18 01:59:46,365 INFO - Configuration loaded from the server at 'localhost:9510'.<br /> [java] 2010-02-18 01:59:46,484 INFO - Configuration loaded from the file at '/var/folders/3R/3Rx0og6cF-KjD-C7zWtiD++++TM/-Tmp-/tc-config7095574028684913201.xml'.<br /> [java] <br /> [java] WARN: The log directory, '/Users/asingh/terracotta/client-logs', is already in use by another Terracotta process. Logging will proceed to the console only.<br /> [java] <br /> [java] 2010-02-18 01:59:48,787 INFO - Connection successfully established to server at 10.0.0.3:9510<br /> [java] Waiting for all nodes to arrive (3 total nodes) ...<br /> [java] This is node-1<br /> [java] Worker-1 is working (may take 10 to 20 secs)... <br /> [java] ==================================<br /> [java] Worker-1: Computed value: "1:is"<br /> [java] ==================================<br /> [java] All workers done! Combining result of all workers (might take some time)...<br /> [java] Final result, after combining results of all workers... <br /> [java] ==================================<br /> [java] this is awesome! <br /> [java] ==================================<br /></pre><br /><br />So what happened is that each of the node is computing a value (here just adding one word from "this is awesome!") to the clustered cache. And after waiting for all nodes using the <bold>clustered</bold> CyclicBarrier, it just builds up the final result "this is awesome!"... and indeed its awesome :)<br /><br /><br />Here's the source of the app:<br /><pre name="code" class="java:collapse"><br />package demo.cyclicbarrier;<br /><br />import java.io.Serializable;<br />import java.util.ArrayList;<br />import java.util.Collections;<br />import java.util.List;<br />import java.util.Random;<br /><br />import net.sf.ehcache.Cache;<br />import net.sf.ehcache.CacheManager;<br />import net.sf.ehcache.Element;<br />import net.sf.ehcache.config.CacheConfiguration;<br />import net.sf.ehcache.config.Configuration;<br />import net.sf.ehcache.config.TerracottaConfigConfiguration;<br />import net.sf.ehcache.config.TerracottaConfiguration;<br /><br />import org.terracotta.express.util.Barrier;<br />import org.terracotta.express.util.ExpressTerracottaClusteredInstanceFactory;<br /><br />public class CyclicBarrierDemo {<br /> private static final Random RANDOM = new Random(System.currentTimeMillis());<br /> private static final String[] words = new String[] { "this", "is", "awesome!" };<br /> private static final String TC_CONFIG_URL = "localhost:9510";<br /><br /> // u need to start 3 nodes for this<br /> public static void main(String[] args) throws Exception {<br /> CyclicBarrierDemo main = new CyclicBarrierDemo();<br /> main.execute();<br /> }<br /><br /> private void execute() throws Exception {<br /> Barrier barrier = new ExpressTerracottaClusteredInstanceFactory(TC_CONFIG_URL)<br /> .createClusteredBarrierAsRoot("clusteredBarrier", words.length);<br /><br /> CacheManager cacheManager = createCacheManagerWithTerracotta(TC_CONFIG_URL, "demoCacheManager");<br /> Cache resultCache = createAndAddTerracottaCache(cacheManager, "resultCache");<br /><br /> System.out.println("Waiting for all nodes to arrive (" + words.length + " total nodes) ...");<br /> // this is a clustered barrier<br /> int index = barrier.barrier();<br /> System.out.println("This is node-" + index);<br /> // do a computation on each node<br /> compute(resultCache, index);<br /><br /> barrier.barrier();<br /> System.out.println("All workers done! Combining result of all workers (might take some time)...");<br /> // imitating long computation<br /> doSleep(10);<br /><br /> List<Value> result = new ArrayList<Value>();<br /> // all the nodes share the result in the clustered cache<br /> for (Object keyObj : resultCache.getKeys()) {<br /> String key = (String) keyObj;<br /> Value v = (Value) resultCache.get(key).getValue();<br /> result.add(v);<br /> }<br /> Collections.sort(result);<br /> System.out.println("Final result, after combining results of all workers... ");<br /> System.out.println("==================================");<br /> for (Value value : result) {<br /> System.out.print(value.word + " ");<br /> }<br /> System.out.println("\n==================================");<br /> }<br /><br /> private Cache createAndAddTerracottaCache(CacheManager cacheManager, String cacheName) {<br /> CacheConfiguration cacheConfig = new CacheConfiguration(cacheName, 1000).eternal(true);<br /> // add terracotta clustered to cache config<br /> cacheConfig.addTerracotta(new TerracottaConfiguration().clustered(true).coherent(true));<br /> // add cache to cache-manager<br /> cacheManager.addCache(new Cache(cacheConfig));<br /> Cache resultCache = cacheManager.getCache(cacheName);<br /> return resultCache;<br /> }<br /><br /> private CacheManager createCacheManagerWithTerracotta(String tcConfigUrl, String cacheManagerName) {<br /> Configuration cacheManagerConfig = new Configuration();<br /> cacheManagerConfig.setDefaultCacheConfiguration(new CacheConfiguration("default", 1000));<br /> cacheManagerConfig.setName(cacheManagerName);<br /> TerracottaConfigConfiguration tcConfigConfiguration = new TerracottaConfigConfiguration();<br /> tcConfigConfiguration.setUrl(tcConfigUrl);<br /> cacheManagerConfig.addTerracottaConfig(tcConfigConfiguration);<br /> CacheManager cacheManager = new CacheManager(cacheManagerConfig);<br /> return cacheManager;<br /> }<br /><br /> private void compute(Cache resultCache, int index) {<br /> System.out.println("Worker-" + index + " is working (may take 10 to 20 secs)... ");<br /> // imitate long computation...<br /> doSleep(RANDOM.nextInt(11) + 10);<br /> Value value = new Value(index, words[index]);<br /> resultCache.put(new Element("key" + index, value));<br /> System.out.println("==================================");<br /> System.out.println("Worker-" + index + ": Computed value: \"" + value + "\"");<br /> System.out.println("==================================");<br /> }<br /><br /> private void doSleep(int secs) {<br /> try {<br /> Thread.sleep(secs * 1000);<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> }<br /><br /> private static class Value implements Serializable, Comparable<Value> {<br /><br /> private final int index;<br /> private final String word;<br /><br /> public Value(int index, String string) {<br /> this.index = index;<br /> this.word = string;<br /> }<br /><br /> public String toString() {<br /> return index + ":" + word;<br /> }<br /><br /> public int compareTo(Value o) {<br /> return this.index - o.index;<br /> }<br /><br /> }<br /><br />}<br /><br /></pre><br /><br />The below snippet is of main interest:<br /><pre name="code" class="java:collapse"><br />Barrier barrier = new ExpressTerracottaClusteredInstanceFactory(TC_CONFIG_URL)<br /> .createClusteredBarrierAsRoot("clusteredBarrier", words.length);<br /><br /> CacheManager cacheManager = createCacheManagerWithTerracotta(TC_CONFIG_URL, "demoCacheManager");<br /> Cache resultCache = createAndAddTerracottaCache(cacheManager, "resultCache")<br /></pre><br /><br /><code>new ExpressTerracottaClusteredInstanceFactory(TC_CONFIG_URL)</code> creates clustered instance factory which you can use to create clustered cyclic barriers. TC_CONFIG_URL is hardcoded to "localhost:9510" for this demo, you can replace this with where you are running your terracotta server.<br /><code>createClusteredBarrierAsRoot("clusteredBarrier", words.length);</code> creates a clustered-cyclic-barrier using the name "clusteredBarrier". So if you create multiple barriers using the same name, its same as sharing the cyclic-barrier cluster-wide. The second argument <tt>words.length</tt> is the number of parties that you want to wait for in each of the nodes, hardcoded to 3 for this demo.<br />Calling barrier.barrier() will not return until the expected number of parties (read threads) have called it. It does not matter whether you are calling from the same JVM or not.... and thats really cool.<br />The next two lines is also an example of how to create Terracotta clustered ehcache cacheManagers and caches. You can take a look at the functions <tt>createCacheManagerWithTerracotta</tt> and <tt>createAndAddTerracottaCache</tt> to see how its done.<br /><br />To run the app, u'll need the following jars:<br /><ul><br /><li>ehcache-core-<version>.jar : ehcache jar file</li><br /><li>slf4j-api-1.5.8.jar : needed for ehcache-core</li><br /><li>slf4j-jdk14-1.5.8.jar : needed for ehcache-core, u can choose other slf4j impls</li><br /><li>ehcache-terracotta-<version>.jar : needed for using terracotta clustered ehcache</li><br /><li>express-util-<version>.jar : Needed for Barrier interface</li><br /><li>express-util-terracotta-<version>.jar : Needed for the ExpressTerracottaClusteredInstanceFactory</li><br /></ul><br /><br /><tt>express-util-<version>.jar</tt> and <tt>express-util-terracotta-<version>.jar</tt> are the new jars that you need to use the clustered CyclicBarrier.<br /><br />Try downloading the sample and running it to get a feel of the real "awesomeness" :)<br />Have fun...<br />By the way, we also use this extensively in our system-tests where we need synchronization between different test threads... threads waiting for each other to complete some operations before asserting things. Its cool and "awesome"!abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com2tag:blogger.com,1999:blog-15536759.post-59000323999319056162009-11-06T18:18:00.005+05:302009-11-06T18:36:39.239+05:30Unique maven surefire plugin configurations for individual tests<script type="text/javascript">var dzone_style = '1';var dzone_url = 'http://abhisanoujam.blogspot.com/2009/11/unique-maven-surefire-plugin.html';</script> <script language="javascript" src="http://widgets.dzone.com/widgets/zoneit.js"></script><br /><br /><br />Here's what I want to do:<br /><br />I have a maven project, which has got multiple unit-tests, db-tests (e.g. DAO tests) and integration/system tests. One of the unit tests require high memory (e.g. 512 MB), but other unit tests runs fine with the default heap size (and doesn't need large heap).<br />I want to run all unit tests by default in the test phase of the build cycle, run the db-tests only when I know a db is present and run the system tests in the integration phase of the build cycle.<br /><br />Lets assume some names for the tests:<br /><br />1) Names of all unit tests end with Test.java, e.g. ClientTest.java, SomeLogicTest.java etc<br />2) The name of the unit test that require high-memory is TestTheWorldWithBigMemoryTest.java. Note that this is not a system tests, its a unit test and just that it requires large heap to run.<br />3) All db-tests end with DaoTest.java, e.g. UserDaoTest.java, CatalogDaoTest.java etc<br />4) All system/integration tests end with SystemTest.java, e.g. AddToShoppingCartSystemTest.java, PurchaseSystemTest.java etc<br /><br />OK, so here's how to configure your pom to do it:<br /><br /><pre name="code" class="xml:collapse"><br /><br /><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br /> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><br /> <modelVersion>4.0.0</modelVersion><br /> <groupId>example</groupId><br /> <artifactId>MySampleProject</artifactId><br /> <packaging>jar</packaging><br /> <version>0.0.1-SNAPSHOT</version><br /> <name>MySampleProject</name><br /> <url>http://www.abhisanoujam.blogspot.com/</url><br /> <dependencies><br /> <dependency><br /> <groupId>junit</groupId><br /> <artifactId>junit</artifactId><br /> <version>3.8.1</version><br /> <scope>test</scope><br /> </dependency><br /> </dependencies><br /><br /> <build><br /> <plugins><br /><br /> <plugin><br /> <groupId>org.apache.maven.plugins</groupId><br /> <artifactId>maven-surefire-plugin</artifactId><br /> <inherited>true</inherited><br /> <configuration><br /> <!--<br /> Skip tests by default. Will be using different executions<br /> setup to run different set of tests with different<br /> configurations<br /> --><br /> <skip>true</skip><br /> <!-- other default configuration for all the tests, just an example --><br /> <reportFormat>plain</reportFormat><br /> <systemProperties><br /> <property><br /> <name>some.property.used.by.tests</name><br /> <value>what.you.want.value</value><br /> </property><br /> </systemProperties><br /> </configuration><br /> <executions><br /> <execution><br /> <!-- run all tests except for system tests and the big memory test --><br /> <id>test-phase-execution</id><br /> <phase>test</phase><br /> <goals><br /> <goal>test</goal><br /> </goals><br /> <configuration><br /> <skip>false</skip><br /> <includes><br /> <include>**/*Test.java</include><br /> </includes><br /> <excludes><br /> <!-- exclude inner classes --><br /> <exclude>**/*$*</exclude><br /> <!-- exclude the test that need large heap --><br /> <exclude>**/TestTheWorldWithBigMemoryTest.java</exclude><br /> <!-- exclude the system-tests --><br /> <exclude>**/*SystemTest.java</exclude><br /> <!-- exclude the db-tests --><br /> <exclude>**/*DaoTest.java</exclude><br /> </excludes><br /> </configuration><br /> </execution><br /> <execution><br /> <!-- Run tests with 512 MB heap --><br /> <id>large-heap-test-execution</id><br /> <phase>test</phase><br /> <goals><br /> <goal>test</goal><br /> </goals><br /> <configuration><br /> <skip>false</skip><br /> <includes><br /> <!--<br /> You can even follow some pattern for these kind of<br /> tests and use the pattern here<br /> --><br /> <!-- For example, **/*BigMemoryTest.java --><br /> <include>**/TestTheWorldWithBigMemoryTest.java</include><br /> </includes><br /> <excludes><br /> <exclude>**/*$*</exclude><br /> </excludes><br /> <argLine>-Xms512m -Xmx512m</argLine><br /> </configuration><br /> </execution><br /> <execution><br /> <!-- Run the system tests in the integration-phase --><br /> <id>system-tests-execution</id><br /> <phase>integration-test</phase><br /> <goals><br /> <goal>test</goal><br /> </goals><br /> <configuration><br /> <skip>false</skip><br /> <includes><br /> <include>**/*SystemTest.java</include><br /> </includes><br /> <excludes><br /> <exclude>**/*$*</exclude><br /> </excludes><br /> </configuration><br /> </execution><br /> </executions><br /> </plugin><br /> </plugins><br /> </build><br /><br /> <profiles><br /><br /> <!-- Profile for running database tests --><br /> <profile><br /> <id>test-db</id><br /> <build><br /> <plugins><br /> <plugin><br /> <artifactId>maven-surefire-plugin</artifactId><br /> <groupId>org.apache.maven.plugins</groupId><br /> <configuration><br /> <!--<br /> Skip tests as the other tests have been already executed<br /> in the "test" and "integration-test" phases<br /> --><br /> <skip>true</skip><br /> </configuration><br /> <executions><br /> <execution><br /> <id>db-test-execution</id><br /> <phase>test</phase><br /> <goals><br /> <goal>test</goal><br /> </goals><br /> <configuration><br /> <skip>false</skip><br /> <includes><br /> <!-- We only need to include the db tests here --><br /> <include>**/*DaoTest.java</include><br /> </includes><br /> </configuration><br /> </execution><br /> </executions><br /> </plugin><br /> </plugins><br /> </build><br /> </profile><br /><br /> </profiles><br /><br /></project><br /><br /><br /></pre><br /><br /><br />I'll try to explain in words (not xml ;-) ) what I did above:<br />By default, maven runs the "test" goal of maven surefire plugin. First we tell it to skip tests by adding the plugin in the <build> section of the pom and specifying <skip>true</skip> in the main configuration of the plugin. Here, you can specify other configurations that you want for your tests as a whole.<br />Then we specify other executions for the plugin and play around with the include/exclude pattern and the configurations for each setup. We can setup multiple executions and also have different set of configurations for each execution setup. Just as a note, adding multiple <plugin> sections for the same plugin in the build section does not work, in fact this is the sole reason of this blog post, and to figure out how to have different configurations depending on your requirements for the same plugin.<br />In the "test-phase-execution", we include all tests with the <tt>**/*Test</tt> and exclude the big memory test, dao tests and the system tests.<br />For the unit-test that requires large heap, its just a matter of adding <tt><argLine>-Xms512m -Xmx512m</argLine></tt> in the configuration for that execution setup. Also we include only that test to run in the include tag.<br />We bind another execution in the "integration-test" phase to run the system tests by matching the include pattern to only system tests.<br /><br />We add another profile "test-db", which we can use when we know we have a database is up and running (you don't want your dao tests to fail all the time during development when you don't have a DB running in your environment). In the profile, we again set up the maven-surefire plugin to execute only the dao tests by playing around with the include/exclude pattern. You can activate this profile whenever you want to run the dao tests.<br /><br />You can check-out a very simple project from <a href="http://abhi-sanoujam-blogspot-posts.googlecode.com/svn/trunk/mavenTestsPomSetup">here</a> and see the above pom in action.<br /><br />Here's how you would run them:<br /><br />mvn clean package<br /> -- This will run all the unit-tests (including the big memory test) but not the system tests and the dao tests<br /><br />mvn -Ptest-db clean package<br /> -- This will the all the unit tests (like above) and also run the dao tests. This still excludes the system test.<br /><br />mvn clean install<br /> -- This will run all the unit tests + the system tests. This won't run the dao tests.<br /> <br />mvn -Ptest-db clean install<br /> -- This will run all the tests -- unit tests, big memory test, dao tests and the system tests<br /> <br />Regarding the system/integration tests, instead of doing like above, you can separate all your system-tests in a separate sub-module too, which I guess is more appropriate when your project is kind of large.<br /><br />Enjoy...abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com11tag:blogger.com,1999:blog-15536759.post-80044011078982396862009-08-25T18:05:00.005+05:302009-08-25T18:39:55.710+05:30... and you say you don't know what's going on in Second-level cache!!<script type="text/javascript">var dzone_style = '1';var dzone_url = 'http://abhisanoujam.blogspot.com/2009/08/and-you-say-you-dont-know-whats-going.html';</script> <script language="javascript" src="http://widgets.dzone.com/widgets/zoneit.js"></script><br /><br />Here it is..<br /><br /><a href="http://terracotta.org/web/display/orgsite/DownloadCatalog">Terracotta 3.1 just got released</a>!! The developer console that comes with this release is ... just awesome!! If you use hibernate and second-level cache... and you had been wondering what's actually going on in your second-level cache.. how many hits, misses, puts... and all those statistical information that you need for your app... those days are over.. well at least if you are using Terracotta :) !! The new console gives you total visibility of your second-level cache...<br /><br />When you open up the dev-console, a new "node" with the name "Hibernate" comes up in the left side tree view of the developer console (when you use second-level cache backed by Terracotta, of course). There you have two views/tabs.. "Hibernate" and "Second level Cache" (the two buttons on top right hand side, as of this release). Hibernate tab gives you stats like how many loads/updates/inserts/deletes etc of your domain entities and collections have happened etc. If you go over to the "Second-level Cache" view, there are tons of other information there. There are 3 sub-views here.. Overview, Statistics and Configuration.<br /><br />Overview shows you how much hits/misses/puts are happening in your cache .. globally (all your cache regions) and per region-wise too. The cool part is that it is real-time.. the picture below might not show it, but its a real-time histogram...<br />In the Statistics panel, you've got 4 charts showing the cache hit ratio, # of cache hit/miss per sec, the # of sql queries happening (this is different from the actual number of queries you get from hibernate statistics, which gives only count of HQL), and the rate at which puts are happening. So if you had ever been wondering if your second-level cache is actually working or not, you can now *prove* its working -- you should see 100% cache hit ratio, no cache misses happening, no queries going to the DB etc. And of course, if these values are not what you expect you should go back and look what's actually going on in your app... the point is you get all those info that you had been wishing for!! isnt' tht sweet... :)<br /><br />Also you can view # hits, # misses, hit ratio etc region wise... which is like the best visibility you can get for your second level cache!! :) <br />You'll have to try out the new dev-console to see what's actually in store for you...<br /><br />Check out some of these screenshots...<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_xg9Zr21WCyc/SpPcowqZB2I/AAAAAAAAAqU/-rBI7HtPQlk/s1600-h/Picture+1.png"><img style="cursor:pointer; cursor:hand;width: 320px; height: 214px;" src="http://2.bp.blogspot.com/_xg9Zr21WCyc/SpPcowqZB2I/AAAAAAAAAqU/-rBI7HtPQlk/s320/Picture+1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5373881373198714722" /></a><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_xg9Zr21WCyc/SpPgs1A77cI/AAAAAAAAAq0/B76kB8eSm-I/s1600-h/Picture+5.png"><img style="cursor:pointer; cursor:hand;width: 320px; height: 218px;" src="http://4.bp.blogspot.com/_xg9Zr21WCyc/SpPgs1A77cI/AAAAAAAAAq0/B76kB8eSm-I/s320/Picture+5.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5373885841133006274" /></a><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_xg9Zr21WCyc/SpPgsYMkSkI/AAAAAAAAAqs/S9ZbW1pDg_M/s1600-h/Picture+4.png"><img style="cursor:pointer; cursor:hand;width: 320px; height: 214px;" src="http://3.bp.blogspot.com/_xg9Zr21WCyc/SpPgsYMkSkI/AAAAAAAAAqs/S9ZbW1pDg_M/s320/Picture+4.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5373885833397160514" /></a><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_xg9Zr21WCyc/SpPgsMpUrcI/AAAAAAAAAqk/7orfBuZB5Eo/s1600-h/Picture+3.png"><img style="cursor:pointer; cursor:hand;width: 320px; height: 214px;" src="http://2.bp.blogspot.com/_xg9Zr21WCyc/SpPgsMpUrcI/AAAAAAAAAqk/7orfBuZB5Eo/s320/Picture+3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5373885830296546754" /></a><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_xg9Zr21WCyc/SpPgrotlpfI/AAAAAAAAAqc/kOgdt4BOhfs/s1600-h/Picture+2.png"><img style="cursor:pointer; cursor:hand;width: 320px; height: 214px;" src="http://1.bp.blogspot.com/_xg9Zr21WCyc/SpPgrotlpfI/AAAAAAAAAqc/kOgdt4BOhfs/s320/Picture+2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5373885820650759666" /></a>abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com1tag:blogger.com,1999:blog-15536759.post-51915104221372593522009-07-07T11:06:00.005+05:302009-07-07T11:49:05.308+05:30Having fun with jquery -- Numbers to wordsHaving some fun with jquery, its cool btw... <br />Type in some numbers in the input box, (it should be in focus when u load this page, if not well u know who to blame - jquery of course, not me :-P)<br />And if you (un)knowingly type in something other than a number, it should tell you whats wrong.<br />Then click "Convert to words" and you'll get your number in words... one in Indian number system and another one in international standard. And, if you are really trying this out, you can use keyboard enter, escape.. i bound them to calculate and hide the results etc.<br /><br />Just some plain short fun :)<br /><br /><script type="text/javascript" src="http://abhi-sanoujam-blogspot-posts.googlecode.com/svn/trunk/js/jquery-1.3.2.min.js"></script> <script type="text/javascript"> function NumberToWords() { var units = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" ]; var teens = [ "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen", "Twenty" ]; var tens = [ "", "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety" ]; var othersIndian = [ "Thousand", "Lakh", "Crore" ]; var othersIntl = [ "Thousand", "Million", "Billion", "Trillion" ]; var INDIAN_MODE = "indian"; var INTERNATIONAL_MODE = "international"; var currentMode = INDIAN_MODE; var getBelowHundred = function(n) { if (n >= 100) { return "greater than or equal to 100"; }; if (n <= 10) { return units[n]; }; if (n <= 20) { return teens[n - 10 - 1]; }; var unit = Math.floor(n % 10); n /= 10; var ten = Math.floor(n % 10); var tenWord = (ten > 0 ? (tens[ten] + " ") : ''); var unitWord = (unit > 0 ? units[unit] : ''); return tenWord + unitWord; }; var getBelowThousand = function(n) { if (n >= 1000) { return "greater than or equal to 1000"; }; var word = getBelowHundred(Math.floor(n % 100)); n = Math.floor(n / 100); var hun = Math.floor(n % 10); word = (hun > 0 ? (units[hun] + " Hundred ") : '') + word; return word; }; return { numberToWords : function(n) { if (isNaN(n)) { return "Not a number"; }; var word = ''; var val; val = Math.floor(n % 1000); n = Math.floor(n / 1000); word = getBelowThousand(val); if (this.currentMode == INDIAN_MODE) { othersArr = othersIndian; divisor = 100; func = getBelowHundred; } else if (this.currentMode == INTERNATIONAL_MODE) { othersArr = othersIntl; divisor = 1000; func = getBelowThousand; } else { throw "Invalid mode - '" + this.currentMode + "'. Supported modes: " + INDIAN_MODE + "|" + INTERNATIONAL_MODE; }; var i = 0; while (n > 0) { if (i == othersArr.length - 1) { word = this.numberToWords(n) + " " + othersArr[i] + " " + word; break; }; val = Math.floor(n % divisor); n = Math.floor(n / divisor); if (val != 0) { word = func(val) + " " + othersArr[i] + " " + word; }; i++; }; return word; }, setMode : function(mode) { if (mode != INDIAN_MODE && mode != INTERNATIONAL_MODE) { throw "Invalid mode specified - '" + mode + "'. Supported modes: " + INDIAN_MODE + "|" + INTERNATIONAL_MODE; }; this.currentMode = mode; } } } function clear() { $("#errSpan").hide(); $("#resultDiv").hide(); } var num2words = new NumberToWords(); function translate() { clear(); var input = $("#input").val(); if (isNaN(input)) { $("#errSpan").html("This is not a number - " + input); $("#errSpan").show(); $("#input").focus(); return; }; num2words.setMode("indian"); var indian = num2words.numberToWords(input); num2words.setMode("international"); var intl = num2words.numberToWords(input); $("#resultDiv").html( "<table bgcolor='#CCFFFF'><tr><td>In India</td><td>" + indian + "</td></tr><tr><td>Internationally</td><td>" + intl + "</td></tr></table>"); $("#resultDiv").show("slow"); } $(document).ready( function() { $("#resultDiv").hide(); $("#input").focus(); $(document).keypress( function(e) { if (e.keyCode == 27) { clear(); }; if (e.keyCode == 13) { translate(); }; }); }); </script> <div id="content" align="center">[you can use enter and escape]<br /> <span id="errSpan" style="color: #FF0000;"></span> <div>Enter a number: <input id="input" type="text" size="15" /><input type="button" onclick="translate()" value="Convert to words" /></div> <div id="resultDiv" style="border: solid black 1px;"></div> </div><br /><br />And yeah, of course, here's the code (use <a href="http://abhisanoujam.blogspot.com/2009/03/posting-javascript-in-blogger_23.html">this link</a> to convert ur javascript into blogger friendly code):<br /><pre name="code" class="javascript:collapse"><br /><br /><script type="text/javascript" src="http://abhi-sanoujam-blogspot-posts.googlecode.com/svn/trunk/js/jquery-1.3.2.min.js"></script><br /><script type="text/javascript"><br /> function NumberToWords() {<br /><br /> var units = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six",<br /> "Seven", "Eight", "Nine", "Ten" ];<br /> var teens = [ "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen",<br /> "Sixteen", "Seventeen", "Eighteen", "Nineteen", "Twenty" ];<br /> var tens = [ "", "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty",<br /> "Seventy", "Eighty", "Ninety" ];<br /><br /> var othersIndian = [ "Thousand", "Lakh", "Crore" ];<br /><br /> var othersIntl = [ "Thousand", "Million", "Billion", "Trillion" ];<br /><br /> var INDIAN_MODE = "indian";<br /> var INTERNATIONAL_MODE = "international";<br /> var currentMode = INDIAN_MODE;<br /><br /> var getBelowHundred = function(n) {<br /> if (n >= 100) {<br /> return "greater than or equal to 100";<br /> };<br /> if (n <= 10) {<br /> return units[n];<br /> };<br /> if (n <= 20) {<br /> return teens[n - 10 - 1];<br /> };<br /> var unit = Math.floor(n % 10);<br /> n /= 10;<br /> var ten = Math.floor(n % 10);<br /> var tenWord = (ten > 0 ? (tens[ten] + " ") : '');<br /> var unitWord = (unit > 0 ? units[unit] : '');<br /> return tenWord + unitWord;<br /> };<br /><br /> var getBelowThousand = function(n) {<br /> if (n >= 1000) {<br /> return "greater than or equal to 1000";<br /> };<br /> var word = getBelowHundred(Math.floor(n % 100));<br /><br /> n = Math.floor(n / 100);<br /> var hun = Math.floor(n % 10);<br /> word = (hun > 0 ? (units[hun] + " Hundred ") : '') + word;<br /><br /> return word;<br /> };<br /><br /> return {<br /> numberToWords : function(n) {<br /> if (isNaN(n)) {<br /> return "Not a number";<br /> };<br /><br /> var word = '';<br /> var val;<br /><br /> val = Math.floor(n % 1000);<br /> n = Math.floor(n / 1000);<br /><br /> word = getBelowThousand(val);<br /><br /> if (this.currentMode == INDIAN_MODE) {<br /> othersArr = othersIndian;<br /> divisor = 100;<br /> func = getBelowHundred;<br /> } else if (this.currentMode == INTERNATIONAL_MODE) {<br /> othersArr = othersIntl;<br /> divisor = 1000;<br /> func = getBelowThousand;<br /> } else {<br /> throw "Invalid mode - '" + this.currentMode<br /> + "'. Supported modes: " + INDIAN_MODE + "|"<br /> + INTERNATIONAL_MODE;<br /> };<br /><br /> var i = 0;<br /> while (n > 0) {<br /> if (i == othersArr.length - 1) {<br /> word = this.numberToWords(n) + " " + othersArr[i] + " "<br /> + word;<br /> break;<br /> };<br /> val = Math.floor(n % divisor);<br /> n = Math.floor(n / divisor);<br /> if (val != 0) {<br /> word = func(val) + " " + othersArr[i] + " " + word;<br /> };<br /> i++;<br /> };<br /> return word;<br /> },<br /> setMode : function(mode) {<br /> if (mode != INDIAN_MODE && mode != INTERNATIONAL_MODE) {<br /> throw "Invalid mode specified - '" + mode<br /> + "'. Supported modes: " + INDIAN_MODE + "|"<br /> + INTERNATIONAL_MODE;<br /> };<br /> this.currentMode = mode;<br /> }<br /> }<br /> }<br /><br /> function clear() {<br /> $("#errSpan").hide();<br /> $("#resultDiv").hide();<br /> }<br /><br /> var num2words = new NumberToWords();<br /><br /> function translate() {<br /> clear();<br /> var input = $("#input").val();<br /> if (isNaN(input)) {<br /> $("#errSpan").html("This is not a number - " + input);<br /> $("#errSpan").show();<br /> $("#input").focus();<br /> return;<br /> };<br /><br /> num2words.setMode("indian");<br /> var indian = num2words.numberToWords(input);<br /><br /> num2words.setMode("international");<br /> var intl = num2words.numberToWords(input);<br /><br /> $("#resultDiv").html(<br /> "<table bgcolor='#CCFFFF'><tr><td>In India</td><td>" + indian<br /> + "</td></tr><tr><td>Internationally</td><td>" + intl<br /> + "</td></tr></table>");<br /> $("#resultDiv").show("slow");<br /> <br /> }<br /><br /> $(document).ready( function() {<br /> $("#resultDiv").hide();<br /> $("#input").focus();<br /> $(document).keypress( function(e) {<br /> if (e.keyCode == 27) {<br /> clear();<br /> };<br /> if (e.keyCode == 13) {<br /> translate();<br /> };<br /> });<br /> });<br /></script><br /><br /><div id="content" align="center">[you can use enter and escape]<br /><br /><span id="errSpan" style="color: #FF0000;"></span><br /><div>Enter a number: <input id="input" type="text" size="15" /><input<br /> type="button" onclick="translate()" value="Convert to words" /></div><br /><br /><div id="resultDiv" style="border: solid black 1px;"></div><br /></div><br /><br /></pre>abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com17tag:blogger.com,1999:blog-15536759.post-45851606146896338312009-06-26T14:42:00.003+05:302009-06-26T15:00:51.028+05:30Meet Mac's "sandbox-exec"I was working on something to block outgoing connections for one particular app *only*. And I use a mac, leopard... the first place I went to look was System Preferences -> Security -> Firewall pane. And I was disappointed to find no "Block Outgoing Connections"<br />It can only block incoming connections. Anyway...<br /><br />Then I met sandbox-exec :) It runs apps in a sandbox, which you can define to restrict access to different resources, network, file-systems etc Here's a simple sandbox file which denies network access...<br /><br /><pre name="code" class="bash:collapse"><br /><br />(version 1)<br />(allow default)<br />(deny network*)<br /><br /></pre><br /><br />Running sandbox-exec with the above file will deny all network access to your app, both outgoing and incoming. Here's a screenshot example showing running bash with and without it:<br /><br /><pre class="console"><br /><br />$ ping -t3 google.com<br />PING google.com (74.125.67.100): 56 data bytes<br />64 bytes from 74.125.67.100: icmp_seq=0 ttl=49 time=368.312 ms<br />64 bytes from 74.125.67.100: icmp_seq=1 ttl=49 time=318.240 ms<br />64 bytes from 74.125.67.100: icmp_seq=2 ttl=49 time=319.793 ms<br /><br />--- google.com ping statistics ---<br />3 packets transmitted, 3 packets received, 0% packet loss<br />round-trip min/avg/max/stddev = 318.240/335.448/368.312/23.247 ms<br />$ <br />$<br />$ cat block-network.sb <br />(version 1)<br />(allow default)<br />(deny network*)<br />$ <br />$ sandbox-exec -f block-network.sb /bin/bash <br />$ ping -t3 google.com<br />bash: /sbin/ping: Operation not permitted<br />$ ping -t3 yahoo.com<br />bash: /sbin/ping: Operation not permitted<br />$ ping -t3 apple.com<br />bash: /sbin/ping: Operation not permitted<br />$ exit<br />exit<br />$ ping -t3 google.com<br />PING google.com (74.125.67.100): 56 data bytes<br />64 bytes from 74.125.67.100: icmp_seq=0 ttl=49 time=321.997 ms<br />64 bytes from 74.125.67.100: icmp_seq=1 ttl=49 time=321.350 ms<br />64 bytes from 74.125.67.100: icmp_seq=2 ttl=49 time=321.676 ms<br /><br />--- google.com ping statistics ---<br />3 packets transmitted, 3 packets received, 0% packet loss<br />round-trip min/avg/max/stddev = 321.350/321.674/321.997/0.264 ms<br />$<br /><br /></pre><br /><br /><br />If you want to write more sophisticated sandbox files, you should probably check out the files present in <tt>/usr/share/sandbox/</tt><br /><br /><br />Enjoy...abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com0tag:blogger.com,1999:blog-15536759.post-26363162531361245322009-06-24T16:40:00.007+05:302009-06-24T18:14:58.219+05:30bash command line editing + custom inputrc -> efficient editing<script type="text/javascript">var dzone_style = '1';var dzone_url = 'http://abhisanoujam.blogspot.com/2009/06/bash-command-line-editing-custom.html';</script> <script language="javascript" src="http://widgets.dzone.com/widgets/zoneit.js"></script><br /><br />Command line editing in bash is sometimes tiring when you have long commands accepting multiple arguments etc. Its hard to edit the command line when you want to change some parameter here and there... adding some parameters in the middle or even modifying existing parameters. I've seen people mostly using the arrow keys to go back and forth and deleting everything and typing in again, or sometimes using copy-paste from previous lines for editing those long long commands. I used to do it myself...<br /><br /><br />But then you can set command line editing modes in bash. This is where it helps a lot in editing those long long commands that you sometimes run.<br /><br />The default mode is set to emacs, and you can choose between using the default or using vi mode. If you want to switch to vi mode, use the following:<br /><pre class="console"><br /><br /> $ set -o vi<br /><br /></pre><br /><br />In vi mode, you can edit the command line as you would normally edit using vi. Press ESC in the command line to switch to vi command mode. And you can go forward editing with all vi habits you got... moving forward-word, backward-word, delete, cut, paste...etc. Typing "i" in command mode will bring you back in vi insert mode.<br /><br />If you are not into vi, you can switch back to emacs mode using:<br /><br /><pre class="console"><br /><br /> $ set -o emacs<br /><br /></pre><br /><br /><br />I have never used emacs myself and being a fan of vi, I used to have vi mode in bash. But I was never happy with that mode.... seemed like too many keystrokes needed to switch between command/insert mode. Also some of the default shortcuts didn't work in vi mode like control-a to move to beginning of line etc.<br /><br />Using emacs mode, you don't need to switch between command/insert mode like in vi. One disadvantage is I don't know emacs and don't wanna learn :)<br /><br />So here's I came up with - default emacs mode and a custom key mapping for most of the common editing functions. Yes, you can specify custom key mappings using a <a href="http://www.faqs.org/docs/bashman/bashref_89.html">.inputrc</a> in your home directory. You should probably go <a href="http://www.faqs.org/docs/bashman/bashref_89.html">here</a> if you want to find out about how to edit/manage your inputrc file....<br /> <br />I guess the most common editing functions that ppl would mostly care about would be (well its true for me at least)<br /><ul><br /><li>Moving forward and backwards by character: Use left/right arrow keys. I guess am too used to using the arrow keys</li><br /><li>Deleting backward and forward characters: Use backspace and delete keys of course, again too used to it</li><br /><li>Moving forward and backwards by word: I didn't like the default mapping of using ESC+b and ESC+f. Felt kind of lazy reaching out for that far-off ESC key :) .... so I changed it to use control-b and control-f instead </li><br /><li>Moving to beginning and end of line: Use the default mappings, control+a to move to beginning and control+e to move to end</li><br /><li>Deleting everything from cursor to beginning: Use default mapping of control+u<br /><br />When you feel like deleting whole of line, use control-e controle-u, i.e. goto end of line and delete everything upto beginning of command</li><br /><li>Deleting one word backward from cursor: Use default control+w<br /><br />If you want to delete the forward word, use control+f control+w, i.e. go forward one word and delete back one word</li><br /><li>Undo: People might not need this normally, but it's handy sometimes. Use default control-x control-u</li><br /></ul><br /><br />Here's the contents of my inputrc file:<br /><br /><pre name="code" class="bash:collapse"><br /><br /># This file controls the behaviour of line input editing for<br /># programs that use the Gnu Readline library. Existing programs<br /># include FTP, Bash, and Gdb.<br />#<br /># You can re-read the inputrc file with C-x C-r.<br /># Lines beginning with '#' are comments.<br />#<br /><br />set editing-mode emacs <br /><br />$if mode=emacs<br /><br />"\C-f": forward-word<br />"\C-b": backward-word<br />"\C-a": beginning-of-line<br />"\C-e": end-of-line<br />"\C-u": unix-line-discard<br />"\C-w": unix-word-rubout<br />"\C-x\C-u": undo<br /><br />$endif<br /><br /><br /></pre><br /><br />As in the comments, if you are editing you inputrc file its helpful to know you can reload your mappings with control-x control-r<br /><br />I'm pretty sure if you have a handy .inputrc file, you won't be frustrated next time when you have one of those long command line and need to edit them, you'll be no longer using the arrow keys :)<br /><br />Enjoy your bash command editing sessions...abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com0tag:blogger.com,1999:blog-15536759.post-33849668436974362152009-04-17T10:42:00.002+05:302009-04-17T10:53:41.057+05:30Browser cached pages + override + refreshThis is one of those "just a quick note..."<br /><br />There are pages in the web which are enabled to be "cached" on the browser and most browsers do cache these pages. But there are times when you want to refresh and get the "latest" copy on the server, at which times what I do normally is open up tools menu for the browser and do something like "clear browser cache". (One place where I normally do this is on the development svn server where the pages are cached, and after creating a branch/adding a directory etc, it doesn't show up.)<br /><br />Here's a quick workaround to refresh the page and get the latest copy from the server overriding the local cached copy in the browser. <br />Just add some random parameters at the end of the page.<br />e.g. If I'm looking at <a href="http://www.abhisanoujam.blogspot.com/">http://www.abhisanoujam.blogspot.com/</a>, what you type is <a href="http://www.abhisanoujam.blogspot.com/?a=b">http://www.abhisanoujam.blogspot.com/?a=b</a> in the browser address bar.<br />This will make sure that your browser actually hits the server bringing back the latest. Don't use the same random parameters as it may as well end up being cached :)<br /><br />For the non-geeks: Don't forget the ? mark before the random parameters...abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com3tag:blogger.com,1999:blog-15536759.post-30785117863964353992009-03-24T17:58:00.006+05:302009-03-24T19:08:58.508+05:30Yahoo and Google Suggestions<script type="text/javascript">var dzone_style = '1';</script> <script language="javascript" src="http://widgets.dzone.com/widgets/zoneit.js"></script><br /> <br />I always loved the suggestions from the google searchbox in firefox. Most of the time the term that I wanted to search almost always came up in the suggestions. I don't remember when was the last time i really went to www.google.com to do some search, have been always using the search box in the toolbar.<br />Had been thinking of having some fun with it since quite some time now...<br /><br />I don't think there's any official API from google for Google Suggest, but looking at iGoogle (it also gives you suggestions real time), there do seemed to be some kind of API. The urls used by Google suggest (as of now) do support callbacks, which gives some opportunity to have fun with it :)<br />Apparently yahoo has got some API for <a href="http://developer.yahoo.com/search/web/V1/relatedSuggestion.html">Related Suggestions</a> (yahoo suggest?). I was playing around with the API's from both google and yahoo... it was fun. Seems to me like google is giving much better results at least compared to yahoo's Related Suggestions. Nonetheless, its just fun looking at the results :)<br /><br />Have fun here... do some typing in the input box below :)<br /><br /><script>var gurl = "http://www.google.com/complete/search?output=json&callback=gcallback&q="; var yurl = "http://search.yahooapis.com/WebSearchService/V1/relatedSuggestion?appid=abhisanoujam&output=json&callback=ycallback&query="; var gsurl = "http://www.google.com/search?q="; var ysurl = "http://search.yahoo.com/search?p="; var gel, yel, lgel, lyel, inp, gres, yres, gh, yh; function init(ename) { var el = document.getElementById(ename); if (el) { inp = document.createElement("input"); inp.id = "inputText"; inp.type = "text"; inp.setAttribute("autocomplete", "off"); inp.onkeyup = keyup; inp.size = 55; inp.value = "Try your text here"; el.appendChild(inp); } var cont = document.createElement("div"); cont .setAttribute("style", "border-style:solid;border-width: 1px;float: left;padding:2px;margin:2px;"); var gcont = document.createElement("div"); gcont .setAttribute( "style", "float: left;padding:2px;margin:2px;width: 300px;height: 300px;border-style:solid;border-width: 1px;"); gh = document.createElement("div"); gh.setAttribute("style", "font-size: 16px;color:#33CC33;"); gh.innerHTML = "Google Suggests"; gcont.appendChild(gh); gres = document.createElement("div"); gcont.appendChild(gres); cont.appendChild(gcont); var ycont = document.createElement("div"); ycont .setAttribute( "style", "float: left;padding:2px;margin:2px;width: 300px;height: 300px;border-style:solid;border-width: 1px;"); yh = document.createElement("div"); yh.setAttribute("style", "font-size: 16px;color:#33CC33;"); yh.innerHTML = "Yahoo Suggests"; ycont.appendChild(yh); yres = document.createElement("div"); ycont.appendChild(yres); cont.appendChild(ycont); el.appendChild(cont); gel = document.createElement("div"); el.appendChild(gel); yel = document.createElement("div"); el.appendChild(yel); keyup(null); } function keyup(evt) { if (emptyCheck()) { return; } var gsel = document.createElement("script"); gsel.setAttribute("type", "text/javascript"); gsel.setAttribute("src", gurl + inp.value); gel.appendChild(gsel); lgel = { "node" :gsel, "text" :inp.value }; var ysel = document.createElement("script"); ysel.setAttribute("type", "text/javascript"); ysel.setAttribute("src", yurl + inp.value); yel.appendChild(ysel); lyel = { "node" :ysel, "text" :inp.value }; } function emptyCheck() { if (trim(inp.value) == "") { var e = new Array(); displayRes(gres, e, null, gh, "Google"); displayRes(yres, e, null, yh, "Yahoo"); return true; } return false; } function gcallback(resp) { if (emptyCheck()) { return; } var res = new Array(); var t = resp[1]; if (t) { for ( var i = 0; i < t.length; i++) { res.push( { "text" :t[i][0], "results" :t[i][1] }); } } displayRes(gres, res, gsurl, gh, "Google"); removeAllChildrenExcept(gel, lgel.node); } function ycallback(resp) { if (emptyCheck()) { return; } var res = new Array(); var t = resp.ResultSet; if (t) { var tt = t.Result; if (tt) { for ( var i = 0; i < tt.length; i++) { res.push( { "text" :tt[i] }); } } } displayRes(yres, res, ysurl, yh, "Yahoo"); removeAllChildrenExcept(yel, lyel.node); } function displayRes(el, r, surl, hh, t) { var h = ''; var l = r.length; for ( var i = 0; i < l; i++) { var fsurl = surl + escape(r[i].text); h += '<span style="font-size: 12px; font-weight: bold;"><a target="_blank" ' + 'style="text-decoration: none;" href="' + fsurl + '"> ' + r[i].text + '</a>'; if (r[i].results) { h += '</span> <span style="font-size: 10px;">' + r[i].results + '</span><br />'; } else h += '<span><br />'; } if (l <= 0) { h = '---'; } el.innerHTML = h; hh.innerHTML = t + ' Suggests (' + l + ' results) ' + getSmiley(l); } function getSmiley(l) { if (l <= 0) { return '<img alt="sad" src="http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/20.gif">'; } else if (l >= 1 && l <= 3) { return '<img alt="smile" src="http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/15.gif">'; } else if (l >= 4 && l <= 7) { return '<img alt="happy" src="http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/1.gif">'; } else return '<img alt="elated" src="http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/69.gif">'; } function removeAllChildrenExcept(node, keepchild) { for ( var i = node.childNodes.length - 1; i >= 0; i--) { if (node.childNodes[i] != keepchild) { node.removeChild(node.childNodes[i]); } } } function trim(str) { return str.replace(/ [ ]*/g, ''); }</script><br /><div id="suggestions"> <script type="text/javascript"> init("suggestions"); </script> </div><br /><br /><div style="clear:both"><br />Oh, by the way, you can click on the results too (will open in a new tab/window)<br /></div><br />And here's the code...<br />The html used for the above is as simple as this:<br /><pre name="code" class="xml:collapse"><br /><div id="suggestions"><br /> <script type="text/javascript"><br /> init("suggestions");<br /> </script><br /></div><br /></pre><br /><br />And here's the actual javascript code thats doing the bulk of the work:<br /><pre name="code" class="xml:collapse"><br />var gurl = "http://www.google.com/complete/search?output=json&callback=gcallback&q=";<br />var yurl = "http://search.yahooapis.com/WebSearchService/V1/relatedSuggestion?appid=abhisanoujam&output=json&callback=ycallback&query=";<br />var gsurl = "http://www.google.com/search?q=";<br />var ysurl = "http://search.yahoo.com/search?p=";<br /><br />var gel, yel, lgel, lyel, inp, gres, yres, gh, yh;<br /><br />function init(ename) {<br /> var el = document.getElementById(ename);<br /> if (el) {<br /> inp = document.createElement("input");<br /> inp.id = "inputText";<br /> inp.type = "text";<br /> inp.setAttribute("autocomplete", "off");<br /> inp.onkeyup = keyup;<br /> inp.size = 55;<br /> inp.value = "Try your text here";<br /> el.appendChild(inp);<br /> }<br /> var cont = document.createElement("div");<br /> cont<br /> .setAttribute("style",<br /> "border-style:solid;border-width: 1px;float: left;padding:2px;margin:2px;");<br /><br /> var gcont = document.createElement("div");<br /> gcont<br /> .setAttribute(<br /> "style",<br /> "float: left;padding:2px;margin:2px;width: 300px;height: 300px;border-style:solid;border-width: 1px;");<br /> gh = document.createElement("div");<br /> gh.setAttribute("style", "font-size: 16px;color:#33CC33;");<br /> gh.innerHTML = "Google Suggests";<br /> gcont.appendChild(gh);<br /> gres = document.createElement("div");<br /> gcont.appendChild(gres);<br /> cont.appendChild(gcont);<br /><br /> var ycont = document.createElement("div");<br /> ycont<br /> .setAttribute(<br /> "style",<br /> "float: left;padding:2px;margin:2px;width: 300px;height: 300px;border-style:solid;border-width: 1px;");<br /> yh = document.createElement("div");<br /> yh.setAttribute("style", "font-size: 16px;color:#33CC33;");<br /> yh.innerHTML = "Yahoo Suggests";<br /> ycont.appendChild(yh);<br /> yres = document.createElement("div");<br /> ycont.appendChild(yres);<br /> cont.appendChild(ycont);<br /><br /> el.appendChild(cont);<br /><br /> gel = document.createElement("div");<br /> el.appendChild(gel);<br /> yel = document.createElement("div");<br /> el.appendChild(yel);<br /> keyup(null);<br />}<br />function keyup(evt) {<br /> if (emptyCheck()) {<br /> return;<br /> }<br /> var gsel = document.createElement("script");<br /> gsel.setAttribute("type", "text/javascript");<br /> gsel.setAttribute("src", gurl + inp.value);<br /> gel.appendChild(gsel);<br /> lgel = {<br /> "node" :gsel,<br /> "text" :inp.value<br /> };<br /><br /> var ysel = document.createElement("script");<br /> ysel.setAttribute("type", "text/javascript");<br /> ysel.setAttribute("src", yurl + inp.value);<br /> yel.appendChild(ysel);<br /> lyel = {<br /> "node" :ysel,<br /> "text" :inp.value<br /> };<br />}<br /><br />function emptyCheck() {<br /> if (trim(inp.value) == "") {<br /> var e = new Array();<br /> displayRes(gres, e, null, gh, "Google");<br /> displayRes(yres, e, null, yh, "Yahoo");<br /> return true;<br /> }<br /> return false;<br />}<br /><br />function gcallback(resp) {<br /> if (emptyCheck()) {<br /> return;<br /> }<br /> var res = new Array();<br /> var t = resp[1];<br /> if (t) {<br /> for ( var i = 0; i < t.length; i++) {<br /> res.push( {<br /> "text" :t[i][0],<br /> "results" :t[i][1]<br /> });<br /> }<br /> }<br /> displayRes(gres, res, gsurl, gh, "Google");<br /> removeAllChildrenExcept(gel, lgel.node);<br />}<br /><br />function ycallback(resp) {<br /> if (emptyCheck()) {<br /> return;<br /> }<br /> var res = new Array();<br /> var t = resp.ResultSet;<br /> if (t) {<br /> var tt = t.Result;<br /> if (tt) {<br /> for ( var i = 0; i < tt.length; i++) {<br /> res.push( {<br /> "text" :tt[i]<br /> });<br /> }<br /> }<br /> }<br /> displayRes(yres, res, ysurl, yh, "Yahoo");<br /> removeAllChildrenExcept(yel, lyel.node);<br />}<br /><br />function displayRes(el, r, surl, hh, t) {<br /> var h = '';<br /> var l = r.length;<br /> for ( var i = 0; i < l; i++) {<br /> var fsurl = surl + escape(r[i].text);<br /> h += '<span style="font-size: 12px; font-weight: bold;"><a target="_blank" '<br /> + 'style="text-decoration: none;" href="'<br /> + fsurl<br /> + '"> '<br /> + r[i].text + '</a>';<br /> if (r[i].results) {<br /> h += '</span> <span style="font-size: 10px;">' + r[i].results + '</span><br />';<br /> } else<br /> h += '<span><br />';<br /> }<br /> if (l <= 0) {<br /> h = '---';<br /> }<br /> el.innerHTML = h;<br /> hh.innerHTML = t + ' Suggests (' + l + ' results) ' + getSmiley(l);<br />}<br />function getSmiley(l) {<br /> if (l <= 0) {<br /> return '<img alt="sad" src="http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/20.gif">';<br /> } else if (l >= 1 && l <= 3) {<br /> return '<img alt="smile" src="http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/15.gif">';<br /> } else if (l >= 4 && l <= 7) {<br /> return '<img alt="happy" src="http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/1.gif">';<br /> } else<br /> return '<img alt="elated" src="http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/69.gif">';<br />}<br /><br />function removeAllChildrenExcept(node, keepchild) {<br /> for ( var i = node.childNodes.length - 1; i >= 0; i--) {<br /> if (node.childNodes[i] != keepchild) {<br /> node.removeChild(node.childNodes[i]);<br /> }<br /> }<br />}<br />function trim(str) {<br /> return str.replace(/ [ ]*/g, '');<br />}<br /></pre><br /><br /><br />Have fun :)abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com5tag:blogger.com,1999:blog-15536759.post-57522123456573605042009-03-23T16:22:00.018+05:302009-03-23T19:37:11.309+05:30Posting javascript in bloggerI once tried to post some javascript inside the post itself and found out that it was not working.
<br />Well seems like if you want to embed <script> tags inside the post, and you have "Convert line breaks" set to true (by default), you cannot add script tags, as Blogger would add <br /> instead of your line breaks.
<br />So you need to write down your script in one single line.
<br />
<br />Which is what this post actually does. Use it to your heart's content :)
<br />Post your javascript here
<br /><script type="text/javascript"> function toggleCheckbox(docid) { var rs = document.getElementById(docid); rs.checked = !rs.checked; } function removeNewLines() { var rnl = document.getElementById("removeNewLines"); var rs = document.getElementById("removeSpace"); var rlt = document.getElementById("replaceLt"); var c = document.getElementById('content').value; var r = rl(c, rnl.checked, rs.checked, rlt.checked); document.getElementById('result').value = r; } function rl(str, rnl, rs, rlt) { if (rnl) { str = str.replace(/[\n\r\t]/g,' '); } if (rs) { str = str.replace(/ [ ]*/g,' '); } if (rlt) { str = str.replace(/</g,'<').replace(/>/g, '>'); } return str; } </script> >
<br /><div>
<br /><textarea id="content" rows="20" cols="60" style="background-color: #FFCC99"></textarea>
<br /></div>
<br /><div>
<br /><input style="cursor:hand;cursor:pointer;" type="checkbox" id="removeNewLines" checked="checked" /><span onclick="toggleCheckbox('removeNewLines');" style="cursor:hand;cursor:pointer;"> Remove New Lines</span><input style="cursor:hand;cursor:pointer;" type="checkbox" id="removeSpace" checked="checked" /><span onclick="toggleCheckbox('removeSpace');" style="cursor:hand;cursor:pointer;"> Remove Multiple spaces</span><input style="cursor:hand;cursor:pointer;" type="checkbox" id="replaceLt" /><span onclick="toggleCheckbox('replaceLt');" style="cursor:hand;cursor:pointer;"> Replace <, > to &lt; and &gt; resp.</span>
<br />
<br /><input type="button" onclick="removeNewLines();" value="Modify!!" />
<br /></div>
<br /><div>
<br /><textarea id="result" rows="15" cols="80" style="background-color: #CCFF99"></textarea>
<br /></div>
<br />Enjoy :)
<br />
<br />BTW, the javascript for the above is embedded in this post itself. And here's the code, use it on the above textarea ;-):
<br /><pre name="code" class="javascript:collapse">
<br /><br /><script type="text/javascript"><br /><br />function toggleCheckbox(docid) {<br />var rs = document.getElementById(docid);<br />rs.checked = !rs.checked;<br />}<br />function removeNewLines() {<br />var rnl = document.getElementById("removeNewLines");<br />var rs = document.getElementById("removeSpace");<br />var rlt = document.getElementById("replaceLt");<br />var c = document.getElementById('content').value;<br />var r = rl(c, rnl.checked, rs.checked, rlt.checked);<br />document.getElementById('result').value = r; <br />}<br /><br />function rl(str, rnl, rs, rlt) {<br />if (rnl) {<br /> str = str.replace(/[\n\r\t]/g,' ');<br />}<br />if (rs) {<br /> str = str.replace(/ [ ]*/g,' ');<br />}<br />if (rlt) {<br /> str = str.replace(/</g,'&lt;').replace(/>/g, '&gt;');<br />}<br />return str;<br />}<br /></script><br />
<br /></pre>abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com8tag:blogger.com,1999:blog-15536759.post-11426833858591728052009-01-30T18:00:00.003+05:302009-01-31T22:21:42.966+05:30Odd Even printing threads... in multiple jvms -- Terracotta wow factor!Recently I got a comment asking about a small use-case demostrating <a href="http://terracotta.org/">Terracotta</a><br /><br />I hacked up a small app in which there are 2 threads (can be more), and each thread is printing numbers sequentially, each thread printing one after another.<br />With 2 threads, one of them will print odd numbers, and the other will print even numbers... when we have multiple number of threads, we want each thread to print one after another, the threads executing serially.<br /><br />So lets take a look at the classes.<br />First, lets have a Counter class that will maintain the current value ...<br /><pre name="code" class="java:collapse"><br />/**<br /> * Jan 28, 2009<br /> * @author abhi.sanoujam<br /> */<br />package sample;<br /><br />public class Counter {<br /> private final int numParties;<br /> private int value = 0;<br /> private final int maxValue;<br /><br /> public Counter(int numParties, int maxValue) {<br /> this.numParties = numParties;<br /> this.maxValue = maxValue;<br /> }<br /><br /> public synchronized boolean isMyTurn(int partyNum) {<br /> return value % numParties == partyNum;<br /> }<br /><br /> public synchronized void setValue(int val) {<br /> this.value = val;<br /> }<br /><br /> public int getMaxValue() {<br /> return maxValue;<br /> }<br /><br /> public synchronized boolean isMaxValueReached() {<br /> return value >= maxValue;<br /> }<br /><br /> public synchronized int increment() {<br /> this.value++;<br /> return value;<br /> }<br /><br /> public synchronized int getValue() {<br /> return value;<br /> }<br />}<br /></pre><br /><br />Here's the Runnable class that each thread will be using:<br /><pre name="code" class="java:collapse"><br /><br />/**<br />* Jan 28, 2009<br />* @author abhi.sanoujam<br />*/<br />package sample;<br /><br />import java.util.concurrent.CyclicBarrier;<br /><br />public class OddEvenRunnable implements Runnable {<br /><br />private final Counter counter;<br />private final int partyId;<br />private final CyclicBarrier barrier;<br /><br />public OddEvenRunnable(int partyId, Counter counter, CyclicBarrier barrier) {<br /> this.partyId = partyId;<br /> this.counter = counter;<br /> this.barrier = barrier;<br />}<br /><br />public void run() {<br /> try {<br /> System.out.println(Thread.currentThread().getName() + ": Waiting for GREEN signal from main guy...");<br /> barrier.await();<br /> } catch (Exception e) {<br /> e.printStackTrace();<br /> }<br /> while (true) {<br /> synchronized (counter) {<br /> while (!(counter.isMyTurn(partyId) || counter.isMaxValueReached())) {<br /> try {<br /> counter.wait();<br /> } catch (InterruptedException e) {<br /> System.out.println(partyId + ": Got Interrupted. Continuing for my turn...");<br /> }<br /> }<br /> if (counter.isMaxValueReached()) {<br /> // make sure other-threads don't keep waiting for my signal when I'm<br /> // leaving<br /> counter.notifyAll();<br /> break;<br /> }<br /> int value = counter.increment();<br /> System.out.println(Thread.currentThread().getName() + ": Counter Value=" + value);<br /> try {<br /> Thread.sleep(500);<br /> } catch (InterruptedException e) {<br /> // ignored<br /> }<br /> counter.notifyAll();<br /> }<br /> }<br /><br /> System.out.println(Thread.currentThread().getName() + ": DONE!!");<br />}<br /><br />}<br /><br /></pre><br /><br /><br />Here's the Main class that drives the app.<br /><br /><pre name="code" class="java:collapse"><br />/**<br /> * Jan 28, 2009<br /> * @author abhi.sanoujam<br /> */<br />package sample;<br /><br />import java.util.HashSet;<br />import java.util.Set;<br />import java.util.concurrent.BrokenBarrierException;<br />import java.util.concurrent.CyclicBarrier;<br /><br />public class Main {<br /><br /> private final int numParties;<br /> <br /> // @Root<br /> private final Counter counter;<br /> // @Root<br /> private final Set<String> runners = new HashSet<String>();<br /> // @Root<br /> private final CyclicBarrier barrier;<br /> private final Thread[] parties;<br /><br /> public Main(int numParties, int maxCounterValue) {<br /> this.numParties = numParties;<br /> counter = new Counter(numParties, maxCounterValue);<br /> barrier = new CyclicBarrier(numParties + 1);<br /> parties = new Thread[numParties];<br /><br /> for (int i = 0; i < numParties; i++) {<br /> parties[i] = new Thread(new OddEvenRunnable(i, counter, barrier), getThreadName(i));<br /> }<br /> }<br /><br /> private void runInSingleJvm() throws BrokenBarrierException, InterruptedException {<br /> // start all the counting parties<br /> for (int i = 0; i < numParties; i++) {<br /> parties[i].start();<br /> }<br /> startCounting();<br /> }<br /><br /> private void startCounting() throws InterruptedException, BrokenBarrierException {<br /> System.out.println(Thread.currentThread().getName() + ": Sleeping for 1 secs....");<br /> Thread.sleep(1000);<br /> System.out.println(Thread.currentThread().getName() + ": ... And letting all the counting threads go!!");<br /> // let thy parties proceed<br /> barrier.await();<br /> }<br /><br /> private String getThreadName(int partyNum) {<br /> String prefix = "";<br /> for (int i = 0; i < partyNum; i++) {<br /> prefix += " ";<br /> }<br /> return prefix + "Party-" + partyNum;<br /> }<br /><br /> public static void main(String[] args) throws Exception {<br /> Main main = new Main(2, 30);<br /> if (args.length == 0) {<br /> // run in a single node/jvm<br /> main.runInSingleJvm();<br /> } else {<br /> if (args.length != 1) {<br /> printUsageAndExit();<br /> }<br /> if ("odd".equals(args[0])) {<br /> main.startFirstThread();<br /> } else if ("even".equals(args[0])) {<br /> main.startSecondThread();<br /> } else if ("main".equals(args[0])) {<br /> main.startMainThread();<br /> } else if ("reset".equals(args[0])) {<br /> main.reset();<br /> } else<br /> printUsageAndExit();<br /> }<br /><br /> }<br /><br /> private void reset() {<br /> for (int i = 0; i < numParties; i++) {<br /> parties[i] = new Thread(new OddEvenRunnable(i, counter, barrier), getThreadName(i));<br /> }<br /> synchronized (runners) {<br /> this.runners.clear();<br /> counter.setValue(0);<br /> }<br /> System.out.println("Reset Done.");<br /> }<br /><br /> private void startMainThread() throws Exception {<br /> if (runners.size() != 2) {<br /> System.out.println("Make sure that you have started both the odd and even threads.");<br /> printUsageAndExit();<br /> }<br /> synchronized (runners) {<br /> runners.add("main");<br /> }<br /> startCounting();<br /> }<br /><br /> private void startSecondThread() {<br /> if (runners.contains("even")) {<br /> System.out.println("You have already started the even-printing thread.");<br /> printUsageAndExit();<br /> }<br /> synchronized (runners) {<br /> runners.add("even");<br /> }<br /> parties[1].start();<br /> System.out.println("Started even thread");<br /> }<br /><br /> private void startFirstThread() {<br /> if (runners.contains("odd")) {<br /> System.out.println("You have already started the odd-printing thread.");<br /> printUsageAndExit();<br /> }<br /> synchronized (runners) {<br /> runners.add("odd");<br /> }<br /> parties[0].start();<br /> System.out.println("Started odd thread");<br /> }<br /><br /> private static void printUsageAndExit() {<br /> System.out.println("USAGE: java Main [odd | even | main | reset]");<br /> System.out.println(" No-arguments - Starts 2 threads printing odd and even values in single jvm.");<br /> System.out.println(" odd - starts the odd-number printing thread in this node.");<br /> System.out.println(" even - starts the even-number printing thread in this node.");<br /> System.out.println(" main - starts a thread which lets the odd and even threads go ahead.");<br /> System.out.println(" reset - Resets all states so you can start all over again.");<br /> System.exit(1);<br /> }<br /><br />}<br /><br /></pre><br /><br /><br />When running in a single-node (without Terracotta), the only method of interest is runInSingleJvm().<br /><br />You can check-out the code, a maven project, from <a href="http://abhi-sanoujam-blogspot-posts.googlecode.com/svn/trunk/terracotta-in-action/">http://abhi-sanoujam-blogspot-posts.googlecode.com/svn/trunk/terracotta-in-action/</a><br />using "svn checkout http://abhi-sanoujam-blogspot-posts.googlecode.com/svn/trunk/terracotta-in-action/ "<br /><br />You can run in a single node like:<br /><pre class="console"><br /><br />$ mvn exec:java -Dexec.mainClass=sample.Main<br /><br /></pre><br />Or you can even use regular java for doing it:<br /><pre class="console"><br /><br />$ mvn compile<br />$ java -cp target/classes/ sample.Main<br /><br /></pre><br /><br />You should be seeing some output like:<br /><pre class="console"><br /><br />sample.Main.main(): Sleeping for 1 secs....<br />Party-0: Waiting for GREEN signal from main guy...<br /> Party-1: Waiting for GREEN signal from main guy...<br />sample.Main.main(): ... And letting all the counting threads go!!<br />Party-0: Counter Value=1<br /> Party-1: Counter Value=2<br />Party-0: Counter Value=3<br /> Party-1: Counter Value=4<br />Party-0: Counter Value=5<br /> Party-1: Counter Value=6<br />Party-0: Counter Value=7<br /> Party-1: Counter Value=8<br />Party-0: Counter Value=9<br /> Party-1: Counter Value=10<br />Party-0: Counter Value=11<br /> Party-1: Counter Value=12<br />Party-0: Counter Value=13<br /> Party-1: Counter Value=14<br />Party-0: Counter Value=15<br /> Party-1: Counter Value=16<br />Party-0: Counter Value=17<br /> Party-1: Counter Value=18<br />Party-0: Counter Value=19<br /> Party-1: Counter Value=20<br />Party-0: Counter Value=21<br /> Party-1: Counter Value=22<br />Party-0: Counter Value=23<br /> Party-1: Counter Value=24<br />Party-0: Counter Value=25<br /> Party-1: Counter Value=26<br />Party-0: Counter Value=27<br /> Party-1: Counter Value=28<br />Party-0: Counter Value=29<br /> Party-1: Counter Value=30<br />Party-0: DONE!!<br /> Party-1: DONE!!<br /><br /></pre><br /><br />We create two threads and the two threads wait on a barrier. The main thread when calls "startCounting()" method, makes the two waiting threads proceed.<br />You can see that the two threads are running sequentially, one by one. Each thread printing the next value on the counter, one thread prints odd numbers and the other thread prints even numbers.<br /><br />Nice and cool. For testing, we can even change the number of parties when calling the Main constructor by passing in the required number of parties. E.g. if we pass in 5, then there will be 5 threads and each thread will print the counter sequentially, the threads executing one after another.<br /><br />Now here comes the wow-after-cool part :)<br />With Terracotta, you can make the threads run on different jvm's and the threads will co-ordinate with each other -- while running in different JVM's. <a href="http://abhisanoujam.blogspot.com/2008/10/definitely-terracotta.html">Imagine doing Object.wait() on one jvm and another thread Object.notify()'ing from another thread</a>... <br /><br />OK, lets have a look at the tc-config.xml for this..., oh by the way, if you are new to Terracotta, you don't need any code-level change to make it work cross-jvm, you just plug in a tc-config and start up with terracotta.<br /><br />Lets have a look at the tc-config for this app:<br /><pre name="code" class="xml:collapse"><br /><br /><?xml version="1.0" encoding="UTF-8"?><br /><tc:tc-config<br /> xmlns:tc="http://www.terracotta.org/config"<br /> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br /> xsi:schemaLocation="http://www.terracotta.org/config http://www.terracotta.org/schema/terracotta-4.xsd"><br /> <servers><br /> <server<br /> name="localhost"<br /> host="localhost"><br /> <dso-port>9510</dso-port><br /> <jmx-port>9520</jmx-port><br /> <data>target/terracotta/server/data</data><br /> <logs>target/terracotta/server/logs</logs><br /> <statistics>target/terracotta/server/statistics</statistics><br /> </server><br /> </servers><br /> <clients><br /> <logs>target/terracotta/clients/logs/%(tc.nodeName)%D</logs><br /> <statistics>target/terracotta/clients/statistics/%(tc.nodeName)%D</statistics><br /> </clients><br /><br /> <application><br /> <dso><br /> <roots><br /> <root><br /> <field-name>sample.Main.counter</field-name><br /> </root><br /> <root><br /> <field-name>sample.Main.runners</field-name><br /> </root><br /> <root><br /> <field-name>sample.Main.barrier</field-name><br /> </root><br /> </roots><br /> <locks><br /> <autolock><br /> <lock-level>write</lock-level><br /> <method-expression>* sample.Main.reset()</method-expression><br /> </autolock><br /> <autolock><br /> <lock-level>write</lock-level><br /> <method-expression>* sample.Main.start*()</method-expression><br /> </autolock><br /> <autolock><br /> <lock-level>write</lock-level><br /> <method-expression>* sample.OddEvenRunnable.run()</method-expression><br /> </autolock><br /> <autolock><br /> <lock-level>write</lock-level><br /> <method-expression>* sample.Counter.increment()</method-expression><br /> </autolock><br /> <autolock><br /> <lock-level>write</lock-level><br /> <method-expression>* sample.Counter.setValue()</method-expression><br /> </autolock><br /> <autolock><br /> <lock-level>read</lock-level><br /> <method-expression>* sample.Counter.isMaxValueReached()</method-expression><br /> </autolock><br /> <autolock><br /> <lock-level>read</lock-level><br /> <method-expression>* sample.Counter.getValue()</method-expression><br /> </autolock><br /> <autolock><br /> <lock-level>read</lock-level><br /> <method-expression>* sample.Counter.isMyTurn()</method-expression><br /> </autolock><br /> </locks><br /> <instrumented-classes><br /> <include><br /> <class-expression>sample.Counter</class-expression><br /> </include><br /> <include><br /> <class-expression>sample.OddEvenRunnable</class-expression><br /> </include><br /> </instrumented-classes><br /> </dso><br /> </application><br /></tc:tc-config><br /><br /></pre><br /><br /><br />You define roots and provide auto-locking for your methods in which you are synchronizing. You should probably go <a href="http://terracotta.org/web/display/docs/Concept+and+Architecture+Guide">here</a> if you want to read more.<br /><br />Roots are basically objects that get shared across the cluster. You can note that the <tt>counter</tt> object is a root, thats how the threads in different nodes are going to get the updated value in each node.<br /><br />For demo purpose, I've written the Main class to take arguments to start up in a parametrized way across the cluster, so that you can start the threads individually across different nodes.<br /><br />Here's the script that I'm using to run with Terracotta (runWithTc.sh). (Again, you can find the whole project <a href="http://abhi-sanoujam-blogspot-posts.googlecode.com/svn/trunk/terracotta-in-action/">here</a>)<br /><br /><pre name="code" class="bash:collapse"><br />#!/bin/bash<br /><br />TC_INSTALL_DIR=/Users/asingh/terracottaKit/terracotta-2.7.1<br /><br />mvn compile<br /><br />CP_FILE=cp.txt<br />mvn dependency:build-classpath -Dmdep.outputFile=$CP_FILE<br />echo ":./target/classes" >> $CP_FILE<br /><br />$TC_INSTALL_DIR/bin/dso-java.sh -cp `cat $CP_FILE` sample.Main $*<br /><br />if [[ -f $CP_FILE ]]<br />then<br /> rm $CP_FILE<br />fi<br /></pre><br /><br />Change <tt>TC_INSTALL_DIR</tt> with the path where you have installed Terracotta.<br /><br />First, you need to start the Terracotta-server. Go to $TC_INSTALL_DIR/bin and type<br /><pre class="console"><br /><br />./start-tc-server.sh<br /><br /></pre><br />This should start the Terracotta server in localhost (note that we have referred to 'localhost' in the server-section in tc-config.xml. You can always run this in multiple machines too if you so desire).<br /><br />To start the Odd printing thread, run it like:<br /><pre class="console"><br /><br />$ ./runWithTc.sh odd<br /><br /></pre><br />You should see an output something like:<br /><pre class="console"><br />Started odd thread<br />Party-0: Waiting for GREEN signal from main guy...<br /></pre><br /><br />Now on another terminal/console, start the Even-printing thread like:<br /><pre class="console"><br /><br />$ ./runWithTc.sh even<br /><br /></pre><br />Again, you should see some output like:<br /><pre class="console"><br />Started even thread<br /> Party-1: Waiting for GREEN signal from main guy...<br /></pre><br /><br />Now you have 2 different threads started in different jvm's and both the threads are waiting on the shared barrier.<br />Lets start the main thread which will make the threads go ahead. Open another terminal/console and type in the following:<br /><pre class="console"><br /><br />$ ./runWithTc.sh main<br /><br /></pre><br /><br />You should be seeing an output like this:<br /><pre class="console"><br />main: Sleeping for 1 secs....<br />main: ... And letting all the counting threads go!!<br /></pre><br /><br />Now, at this point you can see that the other two threads in the other jvm's have started to run. The output in the console's should be like this:<br /><pre class="console"><br />Started odd thread<br />Party-0: Waiting for GREEN signal from main guy...<br />Party-0: Counter Value=1<br />Party-0: Counter Value=3<br />Party-0: Counter Value=5<br />Party-0: Counter Value=7<br />Party-0: Counter Value=9<br />Party-0: Counter Value=11<br />Party-0: Counter Value=13<br />Party-0: Counter Value=15<br />Party-0: Counter Value=17<br />Party-0: Counter Value=19<br />Party-0: Counter Value=21<br />Party-0: Counter Value=23<br />Party-0: Counter Value=25<br />Party-0: Counter Value=27<br />Party-0: Counter Value=29<br />Party-0: DONE!!<br /></pre><br /><br />And for the even-printing node,<br /><pre class="console"><br />Started even thread<br /> Party-1: Waiting for GREEN signal from main guy...<br /> Party-1: Counter Value=2<br /> Party-1: Counter Value=4<br /> Party-1: Counter Value=6<br /> Party-1: Counter Value=8<br /> Party-1: Counter Value=10<br /> Party-1: Counter Value=12<br /> Party-1: Counter Value=14<br /> Party-1: Counter Value=16<br /> Party-1: Counter Value=18<br /> Party-1: Counter Value=20<br /> Party-1: Counter Value=22<br /> Party-1: Counter Value=24<br /> Party-1: Counter Value=26<br /> Party-1: Counter Value=28<br /> Party-1: Counter Value=30<br /> Party-1: DONE!!<br /></pre><br /><br />You can see that the two-threads are co-ordinating across jvm boundaries.<br /><br />Apart from the thread-coordination, this app contains the data-sharing across nodes also. <br />In the tc-config, you can see that we have another root called <tt>runners</tt>. This is a <tt>Set<String></tt> which just remembers which threads have been started.<br />If you run <tt>runWithTc.sh odd</tt> multiple times in multiple consoles, it won't let you start multiple odd-printing threads. It remembers this fact by just putting "odd" in the <tt>runners</tt> which gets shared across the nodes.<br /><br /><br />Hope you have a great time playing around ... and enjoy clustering your app with Terracotta :)<br /><br />P.S. Someday soon, I'm gonna really post something to change things like < to &lt ...abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com9tag:blogger.com,1999:blog-15536759.post-45841502008293785832008-12-03T16:42:00.004+05:302008-12-17T18:44:47.295+05:30Blogger + syntax highlightingI was frustrated to see my <a href="http://abhisanoujam.blogspot.com/2008/11/maven-hibernate-jpa-ehcache-spring.html">last post</a> how blogger was displaying my source-codes for xml, properties file and bash script. It was just un-readable.<br /><br />At last thought I'll clean stuff up a little bit, and also by the way thought of covering up the changes that I made to my blog template for syntax highlighting. Just wanted to keep my changes somewhere, last time forgot to keep a back-up of my working template before editing and had to start all over.<br /><br />I'll be using the <a href="http://code.google.com/p/syntaxhighlighter/">syntax-highlighter</a> to highlight my source-codes in my blog.<br />Things that are needed for this are:<br /><ol><li>Download <a href="http://syntaxhighlighter.googlecode.com/files/SyntaxHighlighter_1.5.1.rar">syntax-highlighter</a> and upload it somewhere in the internet where you have access to. I chose <a href="http://pages.google.com/">google-pages</a> to host my files.<br />The main files that need to be uploaded are:<br /><ul><li>dp.SyntaxHighlighter/Scripts/clipboard.swf<br /></li><li>dp.SyntaxHighlighter/Scripts/shBrushJava.js<br /></li><li>dp.SyntaxHighlighter/Scripts/shCore.js<br /></li><li>dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css<br /></li></ul>I uploaded only the shBrushJava.js brush at start as I thought I would normally be posting only java source-codes. Later I uploaded most of the brushes (including shBrushXml, which was why my earlier post was screwed up).... in fact I'll be creating some new brushes myself today ;-)<br /></li><li> Add links in you blog template to link to the files uploaded in the above step. For this add the following code snippet at the head of your template, just after the <head> tag:<br /><pre name="code" class="javascript:collapse"><br /><link href='http://highlighter.abhi.sanoujam.googlepages.com/SyntaxHighlighter.css' rel='stylesheet' type='text/css'/><br /><script language='javascript' src='http://highlighter.abhi.sanoujam.googlepages.com/shCore.js'/><br /><script language='javascript' src='http://highlighter.abhi.sanoujam.googlepages.com/shBrushJava.js'/><br /><script language='javascript' src='http://highlighter.abhi.sanoujam.googlepages.com/shBrushJScript.js'/><br /><script language='javascript' src='http://highlighter.abhi.sanoujam.googlepages.com/shBrushXml.js'/><br /><script language='javascript' src='http://highlighter.abhi.sanoujam.googlepages.com/shBrushProperties.js'/><br /><script language='javascript' src='http://highlighter.abhi.sanoujam.googlepages.com/shBrushBash.js'/><br /><style type='text/css'><br />.console {<br />background-color: black;<br />color: #ffffff;<br />font-family: courier;<br />}<br /></style><br /></pre><br /><br />Now, replace http://highlighter.abhi.sanoujam.googlepages.com with your own url where you have uploaded your files.<br />Again notice that shBrushProperties.js and shBrushBash.js are actually not present in the syntax-highlighter that you downloaded. We'll create them in this post :)<br />Also we'll talk later about the .console CSS later.<br /></li><li>Activate (call the js function) the highlighter. You can do this by adding the following code snippet at the end of your template just before the </body>tag:<br /><textarea name="code" class="javascript:collapse"><br /><br /><script language='javascript'><br /> dp.SyntaxHighlighter.ClipboardSwf = 'http://highlighter.abhi.sanoujam.googlepages.com/clipboard.swf'; <br /> dp.SyntaxHighlighter.BloggerMode();<br /> dp.SyntaxHighlighter.HighlightAll('code'); <br /></script><br /><br /></textarea></li></ol>Now you are all done to start posting source codes. You can either use the <span style="font-weight: bold;"><pre></span> or <span style="font-weight: bold;"><textarea></span> to enclose your source-codes, and give the name="code" attribute and class="java" for your java source. eg.<br /><br /><pre name="code" class="java"><br />public class HelloBlogger {<br />public static void main(String [] args) {<br /> System.out.println("Hello Blogger!!");<br />}<br />}<br /></pre><br /><br />will look like:<br /><pre name="code" class="java:collapse">public class HelloBlogger {<br />public static void main(String [] args) {<br /> System.out.println("Hello Blogger!!");<br />}<br />}<br /></pre>I personally prefer using the <span style="font-weight: bold;"><pre></span> tag for java and <span style="font-weight: bold;"><textarea></span> tag for posting xml/html codes which normally contains characters like <, ><br /><br />Now here is <a href="http://highlighter.abhi.sanoujam.googlepages.com/shBrushProperties.js">shBrushProperties.js</a><br /><textarea name="code" class="js"><br /><br />dp.sh.Brushes.Properties = function()<br />{<br /> this.regexList = [<br /> { regex: dp.sh.RegexLib.SingleLinePerlComments, css: 'comment' }, // one line comments<br /> { regex: new RegExp('\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b', 'gi'), css: 'number' }, // numbers<br /> ];<br />}<br /><br />dp.sh.Brushes.Properties.prototype = new dp.sh.Highlighter();<br />dp.sh.Brushes.Properties.Aliases = ['properties'];<br /><br /></textarea><br />I just created this brush so that my source for properties file can look nice.<br />Same for my bash scripts, which is <a href="http://highlighter.abhi.sanoujam.googlepages.com/shBrushBash.js">shBrushBash.js</a>:<br /><br /><textarea name="code" class="js"><br /><br />dp.sh.Brushes.Bash = function()<br />{<br /> var keywords = 'break case esac if fi do done in while ' +<br /> 'echo ls touch uname rm ' +<br /> 'mvn svn' +<br /> '';<br /> this.regexList = [<br /> { regex: dp.sh.RegexLib.SingleLinePerlComments, css: 'comment' }, // one line comments<br /> { regex: dp.sh.RegexLib.DoubleQuotedString, css: 'string' }, // strings<br /> { regex: dp.sh.RegexLib.SingleQuotedString, css: 'string' }, // strings<br /> { regex: new RegExp('\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b', 'gi'), css: 'number' }, // numbers<br /> { regex: new RegExp(this.GetKeywords(keywords), 'gm'), css: 'keyword' } // bash keyword<br /> ];<br />}<br /><br />dp.sh.Brushes.Bash.prototype = new dp.sh.Highlighter();<br />dp.sh.Brushes.Bash.Aliases = ['bash'];<br /><br /></textarea><br />You can see that there are only a handful of keywords for the Bash script brush right now. I intend to expand as my blogs keep rolling. For starters, its kinda ok.<br /><br />Now coming to the console CSS style, this is just a small hack for displaying command lines to get a feeling of console/terminals<br />You can use this like;<br /><br /><pre class="console"><br />$ javac HelloBlogger.java<br />$ java HelloBlogger<br />Hello Blogger!!<br /></pre><br /><br />Which will look something like:<br /><pre class="console"><br /><br />$ javac HelloBlogger.java<br />$ java HelloBlogger<br />Hello Blogger!!<br /><br /></pre>You don't need to specify the name attibute :)<br /><br />And ohh, forgot to mention and found out again when clicking "Publish Post", if you are posting XML/HTML, anything which has < or >, replace them with &lt; and &gt; am thinking of putting up a post that will specifically do that.<br /><br />Enjoy...abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com31tag:blogger.com,1999:blog-15536759.post-1666899061407543872008-11-26T18:58:00.001+05:302008-12-17T19:14:06.866+05:30Maven + hibernate + jpa + ehcache + spring + terracotta + composite keysSome user were having some <a href="http://forums.terracotta.org/forums/posts/list/1579.page">problem</a> using composite keys with hibernate and ehcache as second level cache with Terracotta.<br /><br />I tried beefing up a sample app with composite keys so that I can run the app with Terracotta... and it worked out smoothly in a quite small amount of time.<br /><br />I reused much of what we did for <a href="http://reference.terracotta.org/">Examinator</a>, and came up with the app without much pain in very small amount of time. Really, <a href="http://www.terracotta.org/web/display/orgsite/Web+App+Reference+Implementation">Examinator</a> (<a href="http://svn.terracotta.org/repo/forge/projects/exam/trunk/">source</a>) contains quite a lot of things that can be re-used to come up with with these kind of apps.<br /><br />I'll try to put in the main parts of the sample app that I came up here:<br /><br />Used maven-quickstart archetype to generate a quick project skeleton.<br /><br />First the domain classes -- Its a Product class, which is uniquely identified by a combination of its productId and groupId. It also has a description property.<br /><pre name="code" class="java:collapse"><br />package sample.model;<br /><br />import javax.persistence.Column;<br />import javax.persistence.Entity;<br />import javax.persistence.Id;<br />import javax.persistence.IdClass;<br /><br />import org.hibernate.annotations.Cache;<br />import org.hibernate.annotations.CacheConcurrencyStrategy;<br /><br />@Entity<br />@IdClass(ProductCompositeKey.class)<br />@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)<br />public class Product {<br /><br />@Id<br />private Long productId;<br /><br />@Id<br />private Long groupId;<br /><br />@Column(name = "DESCRIPTION")<br />private String description;<br /><br />//...getters and setters...<br /><br />}<br /><br /></pre><br /><br />We are using JPA annotations to define the entities. We annotate the Product class with the @Entity annotation, and as normally, annotate the productId and groupId properties with @Id.<br />We are using hibernate annotation @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) to enable second-level caching on this entity.<br />The @IdClass annotation refers to a class which will be the composite-key class.<br />The ProductCompositeKey is this class and is as follows:<br /><pre name="code" class="java:collapse"><br />package sample.model;<br /><br />import java.io.Serializable;<br /><br />import javax.persistence.Embeddable;<br /><br />@Embeddable<br />public class ProductCompositeKey implements Serializable {<br /><br />private Long productId;<br />private Long groupId;<br /><br />//...getters and setters...<br /><br />@Override<br />public int hashCode() {<br />final int prime = 31;<br />int result = 1;<br />result = prime * result + ((groupId == null) ? 0 : groupId.hashCode());<br />result = prime * result + ((productId == null) ? 0 : productId.hashCode());<br />return result;<br />}<br /><br />@Override<br />public boolean equals(Object obj) {<br />if (this == obj)<br />return true;<br />if (obj == null)<br />return false;<br />if (getClass() != obj.getClass())<br />return false;<br />ProductCompositeKey other = (ProductCompositeKey) obj;<br />if (groupId == null) {<br />if (other.groupId != null)<br />return false;<br />} else if (!groupId.equals(other.groupId))<br />return false;<br />if (productId == null) {<br />if (other.productId != null)<br />return false;<br />} else if (!productId.equals(other.productId))<br />return false;<br />return true;<br />}<br /><br />}<br /><br /></pre><br /><br /><br />The ProductCompositeKey class is annotated with the @Embeddable annotation. Note that it is not annotated with @Entity<br />We need to override equals() and hashcode() and also implement the Serializable interface to make hibernate happy.<br /><br /><br />Next we define DAO classes for the Product. Here's the ProductDao interface:<br /><pre name="code" class="java:collapse"><br />package sample.dao;<br /><br />import java.util.List;<br /><br />import sample.model.Product;<br /><br />public interface ProductDao {<br /><br />public boolean delete(final Product product);<br /><br />public boolean deleteById(final Long id);<br /><br />public Product findById(final Long id);<br /><br />public Product findByName(final String productName);<br /><br />public List<product> getAllProducts();<br /><br />public long getNumberOfProducts();<br /><br />public Product saveOrUpdate(final Product product);<br /><br />}<br /></pre><br /><br />And the ProductDaoImpl class:<br /><pre name="code" class="java:collapse"><br />package sample.dao;<br /><br />import java.util.List;<br /><br />import org.apache.log4j.Logger;<br /><br />import sample.model.Product;<br /><br />public class ProductDaoImpl implements ProductDao {<br /> private static final Logger logger = Logger.getLogger(ProductDaoImpl.class);<br /><br /> final DaoHelper daoHelper;<br /><br /> public ProductDaoImpl(final DaoHelper daoHelper) {<br /> if (null == daoHelper) throw new IllegalArgumentException("daoHelper can't be null");<br /> this.daoHelper = daoHelper;<br /> }<br /><br /> public boolean delete(final Product product) {<br /> if (logger.isDebugEnabled()) logger.debug("delete: " + product.getId());<br /> <br /> return daoHelper.deleteById(Product.class, product.getId());<br /> }<br /><br /> public boolean deleteById(final Long id) {<br /> if (logger.isDebugEnabled()) logger.debug("deleteById: " + id);<br /><br /> return daoHelper.deleteById(Product.class, id);<br /> }<br /><br /> public Product findById(final Long id) {<br /> if (logger.isDebugEnabled()) logger.debug("findById: " + id);<br /><br /> return daoHelper.findById(Product.class, id);<br /> }<br /><br /> public Product findByName(final String productName) {<br /> if (logger.isDebugEnabled()) logger.debug("findByName: " + productName);<br /><br /> final List<Product> list = daoHelper.findByAttribute(Product.class, "name", productName);<br /> if (null == list || 0 == list.size()) return null;<br /> assert list.size() == 1;<br /><br /> return list.get(0);<br /> }<br /><br /> public List<Product> getAllProducts() {<br /> if (logger.isDebugEnabled()) logger.debug("getAllProducts");<br /><br /> return daoHelper.getAllEntities(Product.class);<br /> }<br /><br /> public long getNumberOfProducts() {<br /> if (logger.isDebugEnabled()) logger.debug("getNumberOfProducts");<br /><br /> return daoHelper.countEntities(Product.class);<br /> }<br /><br /> public Product saveOrUpdate(final Product product) {<br /> if (logger.isDebugEnabled()) logger.debug("saveOrUpdate: " + product);<br /><br /> if (null == product) throw new IllegalArgumentException("product can't be null");<br /> if (product.getId() == null) {<br /> return daoHelper.save(Product.class, product);<br /> } else {<br /> return daoHelper.update(Product.class, product);<br /> }<br /> }<br /><br />// public PageData<Product> getProductsByPage(final PageRequest pageRequest) {<br />// if (logger.isDebugEnabled()) logger.debug("getProductsByPage: pageRequest=" + pageRequest);<br />// return daoHelper.getEntitiesByPage(Product.class, pageRequest, "isDeleted", Boolean.FALSE);<br />// }<br />}<br /><br /></pre><br /><br />As you have seen, the ProductDaoImpl class delegates all its work to the DaoHelper class. The DaoHelper class is copied from the <a href="http://svn.terracotta.org/svn/forge/projects/exam/tags/release-1.0.0/src/main/java/org/terracotta/reference/exam/dao/impl/DaoHelper.java">Examinator project</a> (the api's related to paging are commented, as we don't need the paging api's here... hope to come up with a post for the paging soon).<br />The DaoHelper class is given here for reference:<br /><pre name="code" class="java:collapse"><br />package sample.dao;<br /><br />import java.util.List;<br /><br />import javax.persistence.EntityManager;<br />import javax.persistence.PersistenceContext;<br /><br />/**<br /> * The GenericDao is a helper class providing common data access functionality for use (via delegation) by Dao<br /> * implementations.<br /> */<br />public class DaoHelper {<br /><br /> @PersistenceContext<br /> private EntityManager entityManager;<br /><br /> /**<br /> * Defines ASC and DESC sort orders for queries.<br /> */<br /> public enum SortOrder {<br /> ASC, DESC<br /> }<br /><br /> public DaoHelper() {<br /> // entityManager will be set via JSR250 injection<br /> }<br /><br /> /**<br /> * Custom entity manager that will not automatically be injected.<br /> */<br /> public DaoHelper(final EntityManager entityManager) {<br /> this.entityManager = entityManager;<br /> }<br /><br /> public <T> T findById(final Class<T> entityClass, final Object id) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /> if (null == id) throw new IllegalArgumentException("id can't be null");<br /><br /> return entityManager.find(entityClass, id);<br /> }<br /><br /> public boolean delete(final Object entity) {<br /> if (null == entity) throw new IllegalArgumentException("entity can't be null");<br /><br /> entityManager.remove(entity);<br /> return true;<br /> }<br /><br /> public <T> boolean deleteById(final Class<T> entityClass, final Object id) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /> if (null == id) throw new IllegalArgumentException("id can't be null");<br /><br /> return delete(findById(entityClass, id));<br /> }<br /><br /> public int deleteByAttribute(final Class entityClass, final String attributeName, final Object attributeValue) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /> if (null == attributeName) throw new IllegalArgumentException("attributeName can't be null");<br /> if (null == attributeValue) throw new IllegalArgumentException("attributeValue can't be null");<br /><br /> return entityManager.createQuery(<br /> "delete from " + entityClass.getSimpleName() + " e where e." + attributeName<br /> + " = ?1").setParameter(1, attributeValue).executeUpdate();<br /> }<br /><br /> public <T> List<T> findByAttribute(final Class<T> entityClass, final String attributeName, final Object attributeValue) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /> if (null == attributeName) throw new IllegalArgumentException("attributeName can't be null");<br /> if (null == attributeValue) throw new IllegalArgumentException("attributeValue can't be null");<br /><br /> return entityManager.createQuery(<br /> "select e from " + entityClass.getSimpleName() + " e where e." + attributeName<br /> + " = ?1").setParameter(1, attributeValue).getResultList();<br /> }<br /><br /> public <T> List<T> findByAttribute(final Class<T> entityClass, final String attributeName,<br /> final Object attributeValue, final String orderByAttributeName,<br /> final SortOrder sortOrder) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /> if (null == attributeName) throw new IllegalArgumentException("attributeName can't be null");<br /> if (null == attributeValue) throw new IllegalArgumentException("attributeValue can't be null");<br /> if (null == orderByAttributeName) throw new IllegalArgumentException("orderByAttributeName can't be null");<br /><br /> return entityManager.createQuery(<br /> "select e from " + entityClass.getSimpleName() + " e where e." + attributeName<br /> + " = ?1 ORDER BY e." + orderByAttributeName + " " + sortOrder.name())<br /> .setParameter(1, attributeValue).getResultList();<br /> }<br /><br /> public <T> List<T> getAllEntities(final Class<T> entityClass) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /><br /> return entityManager.createQuery("select e from " + entityClass.getSimpleName() + " e").getResultList();<br /> }<br /><br /> public <T> List<T> getAllEntities(final Class<T> entityClass, final String orderByAttributeName,<br /> final SortOrder sortOrder) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /> if (null == orderByAttributeName) throw new IllegalArgumentException("orderByAttributeName can't be null");<br /><br /> return entityManager.createQuery(<br /> "select e from " + entityClass.getSimpleName() + " e order by e."<br /> + orderByAttributeName + " " + sortOrder.name()).getResultList();<br /> }<br /><br /> public <T> T save(final Class<T> entityClass, final T entity) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /> if (null == entity) throw new IllegalArgumentException("entity can't be null");<br /><br /> entityManager.persist(entity);<br /> return entity;<br /> }<br /><br /> public <T> T update(final Class<T> entityClass, final T entity) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /> if (null == entity) throw new IllegalArgumentException("entity can't be null");<br /><br /> return entityManager.merge(entity);<br /> }<br /><br /> public long countEntities(final Class entityClass) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /><br /> return (Long) entityManager.createQuery("select count(entity) from " + entityClass.getSimpleName() + " entity")<br /> .getSingleResult();<br /> }<br /><br /> public long countEntitiesByAttribute(final Class entityClass, final String attributeName, final Object attributeValue) {<br /> if (null == entityClass) throw new IllegalArgumentException("entityClass can't be null");<br /> if (null == attributeName) throw new IllegalArgumentException("attributeName can't be null");<br /> if (null == attributeValue) throw new IllegalArgumentException("attributeValue can't be null");<br /><br /> return (Long) entityManager.createQuery(<br /> "select count(e) from " + entityClass.getSimpleName() + " e where e."<br /> + attributeName + " = ?1").setParameter(1, attributeValue)<br /> .getSingleResult();<br /> }<br /><br /> // public <T> PageData<T> getEntitiesByPage(final Class<T> entityClass, final<br /> // PageRequest pageRequest) {<br /> // return getEntitiesByPage(entityClass, pageRequest, null, null, "id",<br /> // SortOrder.ASC);<br /> // }<br /> //<br /> // public <T> PageData<T> getEntitiesByPage(final Class<T> entityClass, final<br /> // PageRequest pageRequest, final String attributeName,<br /> // final Object attributeValue) {<br /> // return getEntitiesByPage(entityClass, pageRequest, attributeName,<br /> // attributeValue, "id", SortOrder.ASC);<br /> // }<br /> //<br /> // public <T> PageData<T> getEntitiesByPage(final Class<T> entityClass, final<br /> // PageRequest pageRequest, final String attributeName,<br /> // final Object attributeValue, final String orderByAttributeName,<br /> // final SortOrder sortOrder) {<br /> // if (null == entityClass) throw new<br /> // IllegalArgumentException("entityClass can't be null");<br /> // if (null == pageRequest) throw new<br /> // IllegalArgumentException("pageRequest can't be null");<br /> // if (null == orderByAttributeName) throw new<br /> // IllegalArgumentException("orderByAttributeName can't be null");<br /> //<br /> // String queryStr = "";<br /> // if (attributeName != null) {<br /> // queryStr = "select e from " + entityClass.getSimpleName() + " e where e." +<br /> // attributeName + " = ?1 ORDER BY e."<br /> // + orderByAttributeName + " " + sortOrder.name();<br /> // } else {<br /> // queryStr = "select e from " + entityClass.getSimpleName() +<br /> // " e ORDER BY e." + orderByAttributeName + " "<br /> // + sortOrder.name();<br /> // }<br /> // long total;<br /> // if (attributeName != null) total = countEntitiesByAttribute(entityClass,<br /> // attributeName, attributeValue);<br /> // else total = countEntities(entityClass);<br /> // <br /> // final PageRequest newPageRequest =<br /> // PageRequest.adjustPageRequest(pageRequest, total);<br /> // <br /> // Query query;<br /> // if (attributeName != null) query =<br /> // entityManager.createQuery(queryStr).setParameter(1, attributeValue);<br /> // else query = entityManager.createQuery(queryStr);<br /> // final List<T> data = query.setFirstResult(newPageRequest.getStart() -<br /> // 1).setMaxResults(newPageRequest.getPageSize()).getResultList();<br /> // return new PageData<T>(newPageRequest, total, data);<br /> // }<br />}<br /><br /><br /></pre>This is the class that does the main work for talking with your DB through JPA. You can note that the class does not have any compile time dependency on Spring or any other external library except for JPA. So if you are going to work with JPA, this class will make you happy :).<br />If you are thinking about writing your own dao's, you can consider reusing this class, its cool.<br /><br />Next, coming to the service classes, I'll add a ProductService class which can add/remove/list products (from the DB of course). Here's the interface:<br /><pre name="code" class="java:collapse"><br />package sample.service;<br /><br />import java.util.List;<br /><br />import sample.model.Product;<br /><br />public interface ProductService {<br /><br />public void addProduct(Product product);<br /><br />public void deleteProduct(Product product);<br /><br />public List<product> getAllProducts();<br />}<br /></pre><br /><br />The ProductServiceImpl implements the above interface:<br /><pre name="code" class="java:collapse"><br />package sample.service;<br /><br />import java.util.List;<br /><br />import org.springframework.beans.factory.annotation.Autowired;<br />import org.springframework.stereotype.Service;<br />import org.springframework.transaction.annotation.Transactional;<br /><br />import sample.dao.ProductDao;<br />import sample.model.Product;<br /><br />public class ProductServiceImpl implements ProductService {<br /><br />@Autowired<br />private ProductDao productDao;<br /><br />@Transactional(readOnly = false)<br />public void addProduct(Product product) {<br />try {<br />productDao.saveOrUpdate(product);<br />} catch (Exception e) {<br />e.printStackTrace();<br />throw new RuntimeException(e);<br />}<br />}<br /><br />@Transactional(readOnly = false)<br />public void deleteProduct(Product product) {<br />try {<br />productDao.delete(product);<br />} catch (Exception e) {<br />e.printStackTrace();<br />throw new RuntimeException(e);<br />}<br />}<br /><br />@Transactional(readOnly = true)<br />public List<product> getAllProducts() {<br />try {<br />return productDao.getAllProducts();<br />} catch (Exception e) {<br />e.printStackTrace();<br />throw new RuntimeException(e);<br />}<br />}<br /><br />}<br /><br /></pre><br /><br />You can see that I am using Spring's @Transactional annotation to demarcate my transactions. I will show you shortly how it is configured.<br />Am also using the @Autowired annotation, doing so I just need to declare a bean of type ProductDao and Spring will inject the bean into this ProductService bean. Saves me some xml in my application-context file from instead of explicitly setting the dao bean ;-)<br /><br />And here's my application-context file:<br /><br /><pre name="code" class="xml:collapse"><br /><?xml version="1.0" encoding="UTF-8"?><br /><beans xmlns="http://www.springframework.org/schema/beans"<br /> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br /> xmlns:context="http://www.springframework.org/schema/context"<br /> xsi:schemaLocation="<br /> http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd<br /> http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"><br /><br /> <br /> <context:annotation-config /><br /> <br /> <bean id="productService" class="sample.service.ProductServiceImpl" /><br /> <br /> <bean id="daoHelper" class="sample.dao.DaoHelper" /><br /> <bean id="productDao" class="sample.dao.ProductDaoImpl"><br /> <constructor-arg ref="daoHelper" /><br /> </bean><br /> <import resource="data-access.xml"/><br /></beans><br /></pre><br /><br />This just declares my beans and imports the data-access.xml which configures my settings for talking with the DB.<br /><br />Here's the data-access.xml:<br /><textarea name="code" class="xml:collapse"><br /><?xml version="1.0" encoding="UTF-8"?><br /><beans xmlns="http://www.springframework.org/schema/beans" xsi="http://www.w3.org/2001/XMLSchema-instance" tx="http://www.springframework.org/schema/tx" schemalocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"><br /> <br /> <!-- Instructs Spring to perform declarative transaction management on annotated classes --><br /> <tx:annotation-driven><br /> <br /> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><br /> <property name="location" value="classpath:jdbc.properties"><br /> <!-- This PPC does not need to resolve every placeholder; needed to support multiple PPCs --><br /> <property name="ignoreUnresolvablePlaceholders" value="true"><br /> </bean><br /> <br /><br /> <!-- Drives transactions using local JPA APIs --><br /> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"><br /> <property name="entityManagerFactory" ref="entityManagerFactory"><br /> </bean><br /> <br /> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"><br /> <property name="dataSource" ref="dataSource"><br /> <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"><br /> <property name="persistenceUnitName" value="samplewebapp"><br /> <property name="jpaProperties"><br /> <value><br /> # Tooling<br /> hibernate.dialect=${hibernate.dialect}<br /> hibernate.hbm2ddl.auto=${hibernate.hbm2ddl.auto}<br /> <br /> # Debugging / logging<br /> hibernate.show_sql=${hibernate.show_sql}<br /> hibernate.format_sql=${hibernate.format_sql}<br /> hibernate.use_sql_comments=${hibernate.use_sql_comments}<br /> hibernate.generate_statistics=${hibernate.generate_statistics}<br /><br /> # Auto-detect annotated JPA entities<br /> hibernate.archive.autodetection=class<br /> <br /> # Caching<br /> hibernate.cache.provider_class=net.sf.ehcache.hibernate.SingletonEhCacheProvider<br /> hibernate.cache.use_query_cache=false<br /> hibernate.cache.use_second_level_cache=true<br /> </value><br /> </property><br /> </bean><br /> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" method="close"><br /> <!-- Connection Info --><br /> <property name="driverClassName" value="${jdbc.driverClassName}"><br /> <property name="url" value="${jdbc.url}"><br /> <property name="username" value="${jdbc.username}"><br /> <property name="password" value="${jdbc.password}"><br /> <br /> <!-- Connection Pooling DBCP --><br /> <property name="initialSize" value="5"><br /> <property name="maxActive" value="100"><br /> <property name="maxIdle" value="30"><br /> <property name="maxWait" value="1000"><br /> <property name="poolPreparedStatements" value="true"><br /> <property name="defaultAutoCommit" value="false"><br /> </bean><br /> <bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"><br /> <property name="showSql" value="true"><br /> <property name="generateDdl" value="false"><br /> </bean><br /></beans><br /><br /></textarea><br /><br />The <tx:annotation-driven> tag tells Spring to provide transactions to my annotated classes (ProductServiceImpl class).<br /><br />I am configuring my properties from a properties file called "jdbc.properties" from the classpath.<br />I am using commons-dbcp connection pooling library and hence the org.apache.commons.dbcp.BasicDataSource datasource property for the LocalContainerEntityManagerFactoryBean<br /><br />We are using ehcache as the hibernate second-level cache provider.<br />We are setting hibernate.cache.use_second_level_cache to true to enable hibernate second level caching and using net.sf.ehcache.hibernate.SingletonEhCacheProvider as the cache provider.<br /><br />The name of the Persistence unit is samplewebapp as defined in the META-INF/persistence.xml (which basically contains nothing other than the PU name)<br /><textarea name="code" class="xml:collapse"><br /><persistence xmlns="http://java.sun.com/xml/ns/persistence" xsi="http://www.w3.org/2001/XMLSchema-instance" schemalocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"><br /> <persistence-unit name="samplewebapp"><br /></persistence><br /></textarea><br /><br />Here's my jdbc.properties:<br /><pre name="code" class="properties:collapse"><br />## Properties file for JDBC settings<br /><br />##-----------------<br /># MySQL DB Settings<br />##-----------------<br />jdbc.driverClassName=com.mysql.jdbc.Driver<br />jdbc.url=jdbc:mysql://localhost:3306/samplewebapp?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf-8<br />jdbc.username=root<br />jdbc.password=<br /><br /><br />##--------------------<br /># Hibernate properties<br />##--------------------<br />hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect<br />hibernate.hbm2ddl.auto=validate<br /><br /># Debugging<br />hibernate.show_sql=false<br />hibernate.format_sql=true<br />hibernate.use_sql_comments=true<br />hibernate.generate_statistics=false<br /><br /></pre><br /><br /><br />We need to add dependency on all these libraries that we are using - spring, persistence-api (JPA), hibernate, ehcache, dbcp, mysql connector classes etc. This is declared in pom.xml<br /><textarea name="code" class="xml:collapse"><br /><project xmlns="http://maven.apache.org/POM/4.0.0" xsi="http://www.w3.org/2001/XMLSchema-instance" schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><br /> <modelversion>4.0.0</modelversion><br /> <groupid>sample</groupid><br /> <artifactid>hibernateEhcacheTest</artifactid><br /> <packaging>jar</packaging><br /> <version>0.0.1-SNAPSHOT</version><br /> <name>hibernateEhcacheTest</name><br /> <url>http://maven.apache.org</url><br /> <dependencies><br /> <dependency><br /> <groupid>junit</groupid><br /> <artifactid>junit</artifactid><br /> <version>3.8.1</version><br /> <scope>test</scope><br /> </dependency><br /> <dependency><br /> <artifactid>spring-aop</artifactid><br /> <version>${spring.version}</version><br /> <groupid>org.springframework</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>spring-beans</artifactid><br /> <version>${spring.version}</version><br /> <groupid>org.springframework</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>spring-context</artifactid><br /> <version>${spring.version}</version><br /> <groupid>org.springframework</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>spring-core</artifactid><br /> <version>${spring.version}</version><br /> <groupid>org.springframework</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>spring-dao</artifactid><br /> <version>${spring-ext.version}</version><br /> <groupid>org.springframework</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>spring-jdbc</artifactid><br /> <version>${spring.version}</version><br /> <groupid>org.springframework</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>spring-orm</artifactid><br /> <version>${spring.version}</version><br /> <groupid>org.springframework</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>persistence-api</artifactid><br /> <version>${persistence-api.version}</version><br /> <groupid>javax.persistence</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>commons-dbcp</artifactid><br /> <version>${commons-dbcp.version}</version><br /> <scope>compile</scope><br /> <groupid>commons-dbcp</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>hibernate</artifactid><br /> <version>${hibernate.version}</version><br /> <groupid>org.hibernate</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>hibernate-annotations</artifactid><br /> <version>${hibernate-annotations.version}</version><br /> <groupid>org.hibernate</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>hibernate-entitymanager</artifactid><br /> <version>${hibernate-entitymanager.version}</version><br /> <groupid>org.hibernate</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>hibernate-validator</artifactid><br /> <version>${hibernate-validator.version}</version><br /> <groupid>org.hibernate</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>hibernate-commons-annotations</artifactid><br /> <version>${hibernate-commons-annotations.version}</version><br /> <groupid>org.hibernate</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>concurrent</artifactid><br /> <version>${concurrent.version}</version><br /> <groupid>concurrent</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>mysql-connector-java</artifactid><br /> <version>${mysql.version}</version><br /> <scope>compile</scope><br /> <groupid>mysql</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>log4j</artifactid><br /> <version>${log4j.version}</version><br /> <groupid>log4j</groupid><br /> <exclusions><br /> <exclusion><br /> <groupid>javax.jms</groupid><br /> <artifactid>jms</artifactid><br /> </exclusion><br /> <exclusion><br /> <groupid>com.sun.jdmk</groupid><br /> <artifactid>jmxtools</artifactid><br /> </exclusion><br /> <exclusion><br /> <groupid>com.sun.jmx</groupid><br /> <artifactid>jmxri</artifactid><br /> </exclusion><br /> <exclusion><br /> <groupid>javax.mail</groupid><br /> <artifactid>mail</artifactid><br /> </exclusion><br /> </exclusions><br /> </dependency><br /> <dependency><br /> <artifactid>ehcache</artifactid><br /> <version>${ehcache.version}</version><br /> <groupid>net.sf.ehcache</groupid><br /> </dependency><br /> <dependency><br /> <artifactid>tim-hibernate-3.2.5</artifactid><br /> <version>${tim-hibernate-3.2.5.version}</version><br /> <groupid>org.terracotta.modules</groupid><br /> <scope>provided</scope><br /> </dependency><br /> <dependency><br /> <artifactid>tim-ehcache-1.3</artifactid><br /> <version>${tim-ehcache-1.3.version}</version><br /> <groupid>org.terracotta.modules</groupid><br /> <scope>provided</scope><br /> </dependency><br /> </dependencies><br /> <properties><br /> <spring-ext.version>2.0.7</spring-ext.version><br /> <spring.version>2.5.4</spring.version><br /> <persistence-api.version>1.0</persistence-api.version><br /> <commons-dbcp.version>1.2.2</commons-dbcp.version><br /> <hibernate-commons-annotations.version>3.0.0.ga</hibernate-commons-annotations.version><br /> <hibernate-annotations.version>3.3.1.GA</hibernate-annotations.version><br /> <hibernate-entitymanager.version>3.3.1.ga</hibernate-entitymanager.version><br /> <hibernate-validator.version>3.0.0.ga</hibernate-validator.version><br /> <hibernate.version>3.2.5.ga</hibernate.version><br /> <concurrent.version>1.3.4</concurrent.version><br /> <mysql.version>5.0.5</mysql.version><br /> <hibernate3-plugin.version>2.1</hibernate3-plugin.version><br /> <log4j.version>1.2.15</log4j.version><br /> <ehcache.version>1.3.0</ehcache.version><br /> <tim-hibernate-3.2.5.version>1.2.1</tim-hibernate-3.2.5.version><br /> <tim-ehcache-1.3.version>1.2.1</tim-ehcache-1.3.version><br /> </properties><br /> <build><br /> <plugins><br /> <plugin><br /> <artifactid>hibernate3-maven-plugin</artifactid><br /> <version>${hibernate3-plugin.version}</version><br /> <groupid>org.codehaus.mojo</groupid><br /> <configuration><br /> <componentproperties><br /> <implementation>jpaconfiguration</implementation><br /> <propertyfile>target/classes/hibernate3hbm2ddl.properties</propertyfile><br /> <drop>true</drop><br /> <export>true</export><br /> <outputfilename>schema.sql</outputfilename><br /> </componentproperties><br /> </configuration><br /> </plugin><br /> <plugin><br /> <artifactid>maven-compiler-plugin</artifactid><br /> <groupid>org.apache.maven.plugins</groupid><br /> <configuration><br /> <source>1.5</source><br /> <target>1.5</target><br /> </configuration><br /> </plugin><br /> </plugins><br /> <extensions><br /> <extension><br /> <artifactid>mysql-connector-java</artifactid><br /> <version>${mysql.version}</version><br /> <groupid>mysql</groupid><br /> </extension><br /> </extensions><br /> </build><br /> <pluginrepositories><br /> <pluginrepository><br /> <id>terracotta-repository</id><br /> <url>http://www.terracotta.org/download/reflector/maven2</url><br /> <releases><br /> <enabled>true</enabled><br /> </releases><br /> <snapshots><br /> <enabled>true</enabled><br /> </snapshots><br /> </pluginrepository><br /> <pluginrepository><br /> <id>mortbay-snapshot-repo</id><br /> <name>MortBay Snapshot Repo</name><br /> <url>http://jetty.mortbay.org/maven2/snapshot</url><br /> <releases><br /> <enabled>false</enabled><br /> </releases><br /> <snapshots><br /> <enabled>true</enabled><br /> </snapshots><br /> </pluginrepository><br /> </pluginrepositories><br /></project><br /></textarea><br /><br /><br />Now the last thing that we need is the main class that will demonstrate all these glued together.<br />Here's the main class that I am using to drive the App,<br /><br /><pre name="code" class="java:collapse"><br />package sample;<br /><br />import java.util.List;<br />import java.util.Random;<br />import java.util.UUID;<br /><br />import org.springframework.context.ApplicationContext;<br />import org.springframework.context.support.ClassPathXmlApplicationContext;<br /><br />import sample.model.Product;<br />import sample.service.ProductService;<br /><br />public class App {<br /><br />private static Random random = new Random();<br /><br />public static final ApplicationContext ctxt;<br />static {<br /> ctxt = new ClassPathXmlApplicationContext("application-context.xml");<br />}<br /><br />private ProductService productService = (ProductService) ctxt.getBean("productService");<br /><br />public static void main(String[] args) {<br /> new App().test();<br />}<br /><br />private void test() {<br /> listProducts();<br /> addRandomProducts(random.nextInt(10) + 1);<br /> listProducts();<br />}<br /><br />private void addRandomProducts(int numProducts) {<br /> System.out.println("======= Adding " + numProducts + " random products...");<br /> for (int i = 0; i <> allProducts = productService.getAllProducts();<br /> System.out.println("Number of products: " + allProducts.size());<br /> for (Product prod : allProducts) {<br /> System.out.println("Product: " + prod);<br /> }<br />}<br /><br />private Product getRandomProduct() {<br /> Product product = new Product();<br /> product.setProductId(Long.valueOf(UUID.randomUUID().getLeastSignificantBits()));<br /> product.setGroupId(Long.valueOf(UUID.randomUUID().getLeastSignificantBits()));<br /> product.setDescription("A description : " + System.currentTimeMillis());<br /> return product;<br />}<br />}<br /></pre><br /><br /><br />You can try out running this class using<br /><pre class="console"><br /><br />$ mvn exec:java -Dexec.mainClass=sample.App<br /><br /><br /></pre><br /><br /><br />Now the interesting part: clustering the app with Terracotta.<br />For this we create a tc-config.xml and just tell Terracotta that we are using hibernate and ehcache and it will automatically cluster the app.<br /><textarea name="code" class="xml:collapse"><br /><?xml version="1.0" encoding="UTF-8"?><br /><tc:tc-config tc="http://www.terracotta.org/config" xsi="http://www.w3.org/2001/XMLSchema-instance" schemalocation="http://www.terracotta.org/config http://www.terracotta.org/schema/terracotta-4.xsd"><br /> <servers><br /> <server name="localhost" host="localhost"><br /> <dso-port>9510</dso-port><br /> <jmx-port>9520</jmx-port><br /> <data>target/terracotta/server/data</data><br /> <logs>target/terracotta/server/logs</logs><br /> <statistics>target/terracotta/server/statistics</statistics><br /> </server><br /> </servers><br /> <clients><br /> <logs>target/terracotta/clients/logs/%(tc.nodeName)</logs><br /> <statistics>target/terracotta/clients/statistics/%(tc.nodeName)</statistics><br /> <modules><br /> <module name="tim-hibernate-3.2.5" version="1.2.1"><br /> <module name="tim-ehcache-1.3" version="1.2.1"><br /> <module name="clustered-commons-collections-3.1" version="2.7.1"><br /> </modules><br /> </clients><br /> <application><br /> <dso><br /> <instrumented-classes><br /> <include><br /> <class-expression>sample.model.ProductCompositeKey</class-expression><br /> </include><br /> </instrumented-classes><br /> </dso><br /> </application><br /></tc:tc-config><br /><br /></textarea><br /><br />That's all that's needed to cluster with Terracotta.<br />Here's a script that will launch the app with Terracotta, just replace the TC_INSTALL_DIR with the location where you have <a href="http://terracotta.org/web/display/orgsite/DownloadCatalog">downloaded</a> and installed Terracotta:<br /><br /><pre name="code" class="bash:collapse"><br />#!/bin/bash<br /><br />TC_INSTALL_DIR=/Users/asingh/terracottaKit/terracotta-2.7.1<br /><br />mvn compile<br /><br />CP_FILE=cp.txt<br />mvn dependency:build-classpath -Dmdep.outputFile=$CP_FILE<br />echo ":./target/classes" >> $CP_FILE<br /><br />$TC_INSTALL_DIR/bin/dso-java.sh -cp `cat $CP_FILE` sample.App<br /><br />rm $CP_FILE<br /><br /></pre><br /><br /><br />I am sure I must have missed some parts of explaining the glue-points here and there.<br />You can download the attached tarball from <a href="http://forums.terracotta.org/forums/posts/downloadAttach/721.page">here</a> and play around with this simple app.<br /><br />Looks like this was quite a long (hopefully not boring) post. Hope you are still with me and reading this :)... and do leave a comment if you are <span style="font-weight: bold;">not</span> reading this ;-)abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com25tag:blogger.com,1999:blog-15536759.post-71718979147725309322008-11-21T18:49:00.000+05:302008-11-21T18:59:32.868+05:30Just a quick note.... Maven + dependencies + classpathJust wanted to capture this for personal purpose...<br /><br />How to find out/export the classpath from a maven project so that it can be used from scripts etc?<br /><br />-- Using the <a href="http://maven.apache.org/plugins/maven-dependency-plugin/build-classpath-mojo.html">build-classpath goal</a> of the Maven dependency plugin.<br /><br />The parameter name<br /><pre>maven.dep.cpFile</pre> given at the <a href="http://maven.apache.org/plugins/maven-dependency-plugin/usage.html">usage</a> of this goal is deprecated and does not work. Its recommended to use the ouputFile parameter instead.<br /><br />So some examples...<br /><pre>mvn dependency:build-classpath<br /><br /></pre>prints the classpath in the console... and<br /><pre>mvn dependency:build-classpath -Dmdep.outputFile=cp.txt</pre><br />puts the classpath in the file called cp.txtabhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com0tag:blogger.com,1999:blog-15536759.post-84370859848508664382008-11-18T17:49:00.000+05:302008-11-18T20:41:41.818+05:30The webapp -- starting the engineI plan to create a simple webapp and illustrate using maven, hibernate, jpa, spring mvc, spring security, spring webflow etc.<br />And at the last point add Terracotta and see how easy it is to cluster a webapp and scale out.<br /><br />Basically most of the stuffs that I am going to post here are things that I learnt when building <a href="http://reference.terracotta.org/examinator">http://reference.terracotta.org/examinator.</a><br />Yes the Examinator is live. And now you can play around with it.<br />You can find out more about it <a href="http://www.terracotta.org/web/display/orgsite/Web+App+Reference+Implementation"><span style="text-decoration: underline;">here</span>.</a><br /><br />I do not plan to rewrite the whole Examinator here, but show the glue points of making those cool technologies together.<br /><br />Lets get going...<br /><br />First we need a project. I am going to use the <a href="http://forge.terracotta.org/releases/projects/webapp-archetype/index.html">webapp-archetype</a> to generate a skeleton for our project.<br />Using the <a href="http://forge.terracotta.org/releases/projects/webapp-archetype/docs/quickstart.html">quickstart</a> guide, I changed the groupId and the artifactId to generate a project for myself like this:<br /><pre name="code" class="console"><br />mvn org.apache.maven.plugins:maven-archetype-plugin:1.0-alpha-7:create \<br />-DarchetypeGroupId=org.terracotta.maven.archetypes \<br />-DarchetypeArtifactId=webapp-archetype \<br />-DarchetypeVersion=1.2.1 \<br />-DgroupId=<span style="font-weight: bold;">org.sanoujam</span> \<br />-DartifactId=<span style="font-weight: bold;">sample-tc-webapp</span> \<br />-Dversion=1.0.0 \<br />-DremoteRepositories=http://www.terracotta.org/download/reflector/maven2<br /></pre><br />This created a project for me called sample-tc-webapp and generated a working project skeleton for me.<br /><br />If you want, you can verify its running by:<br /><pre name="code" class="console"><br />cd sample-tc-webapp<br />mvn tc:run<br /></pre><br />This will bring up two tomcat nodes on your localhost. Accessing http://localhost:8080/sample-tc-webapp should show you a working web-app which is clustered with Terracotta. The other tomcat is running at http://localhost:8081/sample-tc-webapp<br /><br /><br />You can find the source code of this project at <a href="http://code.google.com/p/sample-tc-webapp/">http://code.google.com/p/sample-tc-webapp/</a><br />The source can be browsed at <a href="http://sample-tc-webapp.googlecode.com/svn/tags/day1/">http://sample-tc-webapp.googlecode.com/svn/tags/day1/</a><br /><br />Stopping here for today...<br />Lets see if we can add Spring next time....abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com0tag:blogger.com,1999:blog-15536759.post-74134831286418818792008-10-29T15:42:00.000+05:302008-10-29T15:55:10.971+05:30ASM Eclipse plugin + Ganymede + Not on its project's build pathASM plugin for eclipse found <a href="http://asm.objectweb.org/eclipse/index.html">here</a> works neat upto Eclipse 3.3<br />If you have updated to Eclipse Ganymede, you will see that it no longer works.<br />It complains something like:<br /><code><br />Error (Bytecode Outline) src/Foo.java [in BarProject] is not on its project's build path.<br /></code><br /><br />Where Foo.java is the java class for which you want to view the bytecode and MyProject is the eclipse project in which it is present.<br /><br />After a little bit of googling found out that a new version of the plugin is available for Eclipse Ganymede. <div>You can get the new version at <a href="http://andrei.gmxhome.de/bytecode/index.html">http://andrei.gmxhome.de/bytecode/index.html</a>.<br />Use <a href="http://andrei.gmxhome.de/eclipse/">http://andrei.gmxhome.de/eclipse/</a> as the update URL with the update manager and install the new version.<br /><br />Works cool now :)</div>abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com11tag:blogger.com,1999:blog-15536759.post-23103818261751198132008-10-03T03:57:00.000+05:302008-10-03T04:41:09.175+05:30Definitely Terracotta<blockquote><br />Imagine being able to call <tt>wait()</tt> on a Java object in one JVM, and imagine that later a thread in another JVM on another computer calls <tt>notify()</tt> on that same object and that <tt>notify()</tt> call penetrates the impermeable process boundary between the two JVMs to wake your thread from its slumber. Further imagine that all of the changes to that object made in the other JVM while you were waiting are now visible to your thread. Imagine now that the code you wrote to make this happen looked no <i>different</i> than code you would have written were this program to run only on a single JVM—no concessions to special frameworks, no callouts to special APIs, no stubs, no skeletons, no copies, no put-backs, and no magic beans.<br /><br />What would it be like if you could automatically share Java heap between virtual machines so that threads running in different JVMs could interact with each other exactly as if they were running in the same JVM? Imagine what you could do with that kind of power -— the power to express a computing problem in the simplest possible terms that stay true to the problem at hand yet be able to run that program on multiple computers at once while the program thinks it’s running on one big computer. What would such a thing be? And wouldn’t you really, really want it if you could have it?<br /><br />Terracotta was born out of just such imaginings.<br />Terracotta is Java infrastructure software that allows you to scale your application to as many computers as needed, without expensive custom code or databases. Throughout this book, it is referred to as JVM-level clustering or network-attached memory. The question you now face is how to understand and leverage this technology best.<br /></blockquote><br /><br />Doesn't that sound cool? Its an excerpt from the introduction in <a href="http://www.amazon.com/Definitive-Guide-Terracotta-Hibernate-Scalability/dp/1590599861/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1222986439&sr=8-1">The Definitive Guide to Terracotta</a> about <a href="http://terracotta.org/">Terracotta</a> from the same people who made all this imaginations a reality.<br /><br />I am working for <a href="http://terracottatech.com/">Terracotta</a> these days, and since joining had been doing real cool stuff. <br /><br />We are developing a reference app these days on the <a href="http://tech.puredanger.com/2008/07/08/narrowing-the-focus/">sessions-clustering use case</a>. Its not only about developing a web-app and clustering a web application with Terracotta, but get out the best practices and design patterns and best ways to solve problems for similar use cases, to say the least. We had focussed down on the <a href="http://tech.puredanger.com/2008/07/29/settling-on-a-stack/">technology stack</a> and almost done developing the app. <br /><br />In the process, I learnt a lot personally and got exposed to cool new technologies and how to leverage Terracotta. I plan to share my experience and hope that it helps others out there.<br /><br />More posts coming up on Spring, Spring MVC, Spring Web-flow, Spring security, JPA, hibernate, maven... and other cool stuffs!!<br /><br />Stay tuned... ;-)abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com0tag:blogger.com,1999:blog-15536759.post-4198107276175245412008-09-10T02:19:00.000+05:302008-09-10T17:25:07.438+05:30ASM - Searching methods for specific OpcodesDoing bytecode manipulation is cool, and <a href="http://asm.objectweb.org/">ASM</a> makes the job quite easy. Its a cool framework that lets us do bytecode manipulation, analyse, modify or even dynamically generate bytecodes on the fly.<br />The <a href="http://download.forge.objectweb.org/asm/asm-guide.pdf">User guide</a> on their web-site is quite a nice doc and explains things neatly.<br /><br />ASM is quite simple and easy to use. Following is a simple Class adapter that can be used to search for methods which contains specific Opcodes. I actually intend to write a use case about resolving types of AALOAD, but I guess I'll cover this simple Adapter first which will be used later on.<br /><br />So here goes...<br /><pre name="code" class="java"><br />package adapters;<br /><br />import org.objectweb.asm.ClassAdapter;<br />import org.objectweb.asm.ClassVisitor;<br />import org.objectweb.asm.MethodVisitor;<br />import org.objectweb.asm.Opcodes;<br />import org.objectweb.asm.MethodAdapter;<br /><br />import java.util.HashMap;<br />import java.util.Map;<br /><br />//a class adapter that will search for specific opcode in all its methods and remember those<br />//methods<br />public class OpcodeSearchClassAdapter extends ClassAdapter implements Opcodes {<br /><br />// the opcode to be searched as defined in com.objectweb.asm.Opcodes<br />private int searchOpcode;<br />// mapping of method names -> method signatures<br />private Map methodsWithSearchOpcode;<br />private boolean done;<br /><br />public OpcodeSearchClassAdapter(final ClassVisitor classVisitor, int searchOpCode) {<br /> super(classVisitor);<br /> methodsWithSearchOpcode = new HashMap();<br /> searchOpcode = searchOpCode;<br />}<br /><br />public MethodVisitor visitMethod(final int access, final String name, final String desc,<br /> final String signature, final String[] exceptions) {<br /> final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);<br /> return new OpcodeSearchMethodAdapter(access, name, desc, mv, searchOpcode,<br /> methodsWithSearchOpcode);<br />}<br /><br />public void visitEnd() {<br /> this.done = true;<br /> super.visitEnd();<br />}<br /><br />public int getSearchOpcode() {<br /> return searchOpcode;<br />}<br /><br />public Map getMethodsWithSearchOpcode() {<br /> if (!done) {<br /> return null;<br /> }<br /> return methodsWithSearchOpcode;<br />}<br /><br />private static class OpcodeSearchMethodAdapter extends MethodAdapter implements Opcodes {<br /><br /> private Map methodsWithOpcode;<br /> private int searchOpcode;<br /> private String name;<br /> private String desc;<br /><br /> private OpcodeSearchMethodAdapter(final int access, final String name, final String desc,<br /> final MethodVisitor mv, final int searchOpcode, final Map methodsWithOpcode) {<br /> super(mv);<br /> this.name = name;<br /> this.desc = desc;<br /> this.searchOpcode = searchOpcode;<br /> this.methodsWithOpcode = methodsWithOpcode;<br /> }<br /><br /> public void visitInsn(final int opcode) {<br /> if (opcode == searchOpcode) {<br /> this.methodsWithOpcode.put(name + " " + desc, name);<br /> }<br /> super.visitInsn(opcode);<br /> }<br />}<br />}<br /><br /></pre><br /><br />Lets write a simple example that demonstrates this class adapter. We'll write a simple Test class that will contain some methods with the instruction that we want to search for. We will then load the bytes of this Test class, go through the bytecode of this class and look inside all the methods searching for the required Opcode instruction.<br />For example, lets say we are going to search for the AALOAD instruction, lets write our Test class as follows:<br /><pre name="code" class="java"><br />package test;<br /><br />public class TestClass {<br /><br /> public void fooSquare(Integer[] bar, int i) {<br /> System.out.println("Method with AALOAD");<br /> bar[i] = new Integer(bar[i].intValue() * bar[i].intValue());<br /> }<br /><br /> public int getFoo(int[] bar, int i) {<br /> System.out.println("Method with IALOAD, not with AALOAD");<br /> return bar[i];<br /> }<br /> <br /> public void foobar() {<br /> System.out.println("Method with no AALOAD");<br /> }<br /> <br /> public void foo(Foo[] fooArr, int i) {<br /> System.out.println("Another method with AALOAD");<br /> System.out.println("Invokes method on the array element");<br /> fooArr[i].bar();<br /> }<br /> <br /> public static class Foo {<br /> public void bar() {<br /> System.out.println("Foo.bar()");<br /> }<br /> }<br /><br />}<br /></pre><br />The TestClass contains some methods with AALOAD. It also contains other methods which doesn't contain it. The methods <code>public void fooSquare(Integer[] bar, int i)</code> and <code>public void foo(Foo[] fooArr, int i)</code> are the methods that contain the AALOAD instruction as seen in the code.<br /><br />Lets now write the Main class that will load the bytes of <code>TestClass</code> and run the <code>OpcodeSearchClassAdapter</code><br /><br /><pre class="java" name="code"><br />package test;<br /><br />import java.io.IOException;<br />import java.io.InputStream;<br />import java.util.Iterator;<br />import java.util.Map;<br /><br />import org.objectweb.asm.ClassReader;<br />import org.objectweb.asm.ClassVisitor;<br />import org.objectweb.asm.Opcodes;<br />import org.objectweb.asm.commons.EmptyVisitor;<br /><br />import adapters.OpcodeSearchClassAdapter;<br /><br />public class MainTest {<br /><br /> public static void main(String[] args) throws IOException {<br /> new MainTest().test();<br /> }<br /><br /> private void test() throws IOException {<br /> String testClassName = TestClass.class.getName().replace('.', '/');<br /> String fileName = testClassName.substring(testClassName.lastIndexOf('/') + 1) + ".class";<br /> InputStream is = this.getClass().getResourceAsStream(fileName);<br /> ClassReader reader = new ClassReader(is);<br /> ClassVisitor emptyVisitor = new EmptyVisitor();<br /> OpcodeSearchClassAdapter searchClassAdapter = new OpcodeSearchClassAdapter(emptyVisitor,<br /> Opcodes.AALOAD);<br /> reader.accept(searchClassAdapter, ClassReader.SKIP_FRAMES);<br /><br /> System.out.println("Methods with AALOAD: ");<br /> Map methods = searchClassAdapter.getMethodsWithSearchOpcode();<br /> for (Iterator it = methods.keySet().iterator(); it.hasNext();) {<br /> String key = (String) it.next();<br /> String val = (String) methods.get(key);<br /> System.out.println("method name: " + val + " desc:" + key);<br /> }<br /> }<br />}<br /></pre><br /><br />The MainTest class loads the TestClass and runs our class adapter through the TestClass bytecode.<br />It first creates an InputStream of the TestClass bytecodes by loading the bytes using getResourceAsStream method in ClassLoader. A ClassReader is instantiated using the inputstream which "accepts" an instance of OpcodeSearchClassAdapter. We create an instance of OpcodeSearchClassAdapter which takes an EmptyVisitor and the Opcode we are searching for -- which is AALOAD in this example.<br />We are using an EmptyVisitor as we are only reading through the bytecode and not changing anything. The methods containing the Opcode we are searching is remembered by the OpcodeSearchClassAdapter. <br /><br />After the reader accepts the Adapter, we use getMethodsWithSearchOpcode() in the OpcodeSearchClassAdapter to get the methods which contain AALOAD and simply print it out.<br /><br />If you compile and run the above, you will get an output like:<br /><pre class="js" name="code"><br />Methods with AALOAD: <br />method name: foo desc:foo ([Ltest/TestClass$Foo;I)V<br />method name: fooSquare desc:fooSquare ([Ljava/lang/Integer;I)V<br /></pre><br /><br />In the next post I will show how to analyse bytecode and get the type reference of AALOAD which can be used to instrument this access.abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com0tag:blogger.com,1999:blog-15536759.post-1591663394014744592008-06-26T15:02:00.000+05:302008-09-10T21:56:43.748+05:30"bash: clear: command not found" --- clear not working on cygwinOk so you installed Cygwin and try out your first commands.... u cluttered ur screen and its time for doing a "clear".<br /><br />Ooops... its giving you a "bash: clear: command not found". You just can't clear the dumb screen. I know thats frustrating....<br /><br />So which dumb package did you miss when installing cygwin --- its the ncurses package. Open cygwin setup again and choose ncurses library from the "Lib" package. Once you've installed that, clear will start working.<br /><br />If you are lazy enough and don't want to download stuff, just use "Ctrl+L" to clear your screen.<br /><br /><br />Keep clearing...abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com28tag:blogger.com,1999:blog-15536759.post-7712307602541523082008-06-26T14:55:00.000+05:302008-09-10T21:57:38.200+05:30Setting up ssh server on cygwinHere's a nice post I came up with when I was looking on how to set up ssh server up and running on my win box with Cygwin.<br /><br /><a href="http://www.blogger.com/%20http://lifehacker.com/software/home-server/geek-to-live--set-up-a-personal-home-ssh-server-205090.php"> Setting up ssh server on cygwin</a><br /><br />After setting up ssh server and starting it, use putty to connect to ur own localhost. It feels much better using putty than cmd.exe to use the shell. cmd sux !!<br /><br />Enjoyabhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com0tag:blogger.com,1999:blog-15536759.post-1124342359746573292005-08-18T10:47:00.000+05:302005-08-18T10:49:19.750+05:30ummmaaauuuummmmm......!!...??.....mmmm....abhi.sanoujamhttp://www.blogger.com/profile/15055282868290240768noreply@blogger.com1