<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" gd:etag="W/&quot;DUMCRH4-eCp7ImA9WhdRE08.&quot;"><id>tag:blogger.com,1999:blog-8172024450972453037</id><updated>2011-08-03T01:24:25.050+02:00</updated><category term="Tomcat" /><category term="JPA" /><category term="osgi" /><category term="UTF8" /><category term="javascript" /><category term="java" /><category term="configuration" /><category term="plugin" /><category term="UTF-8" /><category term="organisation hierarchy" /><category term="character encoding" /><category term="tree drawing" /><category term="raphael js" /><category term="ruby rails" /><category term="MySql" /><category term="Spring" /><category term="extensibility" /><title>Here, there, wherever...</title><subtitle type="html">Welcome to my BLOG. My name is Mathias. I live and work in Switzerland. I am using this BLOG to share useful code snippets.&lt;br&gt;&lt;br&gt;

If you like what you see, write a comment! If you don't like what you see, write a comment, too!&lt;br&gt;&lt;br&gt;

Needless to say, whatever I express on this blog are my views not these of my employer or any other organisation I may be affiliated with in any way at any point in time.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://mathiasrichter.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://mathiasrichter.blogspot.com/" /><author><name>Mathias</name><uri>http://www.blogger.com/profile/13180258123551210286</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://3.bp.blogspot.com/_fNAEyWEDYJQ/Srfk1UYYVfI/AAAAAAAAAAM/qtiafPzZGZU/S220/Mathias.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>9</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/HereThereWherever" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="heretherewherever" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;DEICSXk7cCp7ImA9Wx5QEE8.&quot;"><id>tag:blogger.com,1999:blog-8172024450972453037.post-2526942764277523231</id><published>2010-08-26T23:20:00.008+02:00</published><updated>2010-08-28T22:16:08.708+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-08-28T22:16:08.708+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="raphael js" /><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="tree drawing" /><category scheme="http://www.blogger.com/atom/ns#" term="ruby rails" /><category scheme="http://www.blogger.com/atom/ns#" term="organisation hierarchy" /><title>Drawing trees with Raphael.js and Ruby</title><content type="html">It's been quite some time since I last posted (I just realise I open every post like this - guess I'm one of the laziest bloggers out there :-).&lt;br /&gt;&lt;br /&gt;I've been busy hacking Ruby and thought I share this. For a website I'm currently building, I needed to draw organisation and process hierarchies (ie. any type of tree or forest) in the browser. the drawings need to be interactive (ie. clickable for getting a node's details).&lt;br /&gt;&lt;br /&gt;First challenge was how to do the drawing. Generate the image server side, but then what about clickable areas in the image? Draw the image client side, but then I really didn't want to implement a tree drawing algorithm in JavaScript...&lt;br /&gt;&lt;br /&gt;I did some research about what's out there in the wild and came across &lt;a href="http://raphaeljs.com/"&gt;Raphael.js&lt;/a&gt; (kudos Dmitry, great job!). Raphael is a clean, simple graphics API for JavaScript.&lt;br /&gt;&lt;br /&gt;But I still didn't want to implement tree drawing in JavaScript, so came to the conclusion to implement the tree-drawing in Ruby so as to generate the JavaScript.&lt;br /&gt;&lt;br /&gt;The result is further down in this post. &lt;tt&gt;TreeDraw&lt;/tt&gt; is a class to which you pass a tree or forest (I'm using &lt;a href="http://github.com/rails/acts_as_tree"&gt;acts_as_tree&lt;/a&gt; to get methods like &lt;tt&gt;children&lt;/tt&gt;, &lt;tt&gt;ancestors&lt;/tt&gt;, &lt;tt&gt;parent&lt;/tt&gt;) as well as some drawing options (optional, defaults are all defined).&lt;br /&gt;&lt;br /&gt;You can also provide a base URL (e.g. "&lt;tt&gt;/mycontroller/myaction/&lt;/tt&gt;") which will be augmented with a node's ID to become a link to viewing a node's details (e.g. "&lt;tt&gt;/mycontroller/myaction/5&lt;/tt&gt;").&lt;br /&gt;&lt;br /&gt;The code looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"&gt;&lt;code&gt;&lt;br /&gt;# The TreeDraw class draws trees using the Raphael Javascript drawing library.&lt;br /&gt;class TreeDraw&lt;br /&gt;&lt;br /&gt;  include ActionView::Helpers::TextHelper&lt;br /&gt;  include ERB::Util&lt;br /&gt;&lt;br /&gt;  # Construct a new Tree drawing instance following the Reingold-Tilford-Algorithm. Parameters are:&lt;br /&gt;  # - roots =&gt; The forest to draw (array of roots of the forest)&lt;br /&gt;  # - link =&gt; If specified, then each box will link to the specified link with the id of the specific node appended&lt;br /&gt;  # - options =&gt; a hash allowing to override the various default drawing options&lt;br /&gt;  def initialize(roots, link=nil, options = {})&lt;br /&gt; @roots = roots # the roots of the tree where each node responds to methods like "children", "parent", "ancestors"&lt;br /&gt; @link = link&lt;br /&gt; init_options(options)&lt;br /&gt; @x_pos = {} # hash to hold the x positions for each node&lt;br /&gt; @x_mod = {} # hash to hold the x position modifiers for each node&lt;br /&gt; @x_cumul_mod = {}&lt;br /&gt; @y_pos = {} # hash to hold the y positions for each node&lt;br /&gt; @max_width = {} # hash to hold the max width of each root's tree&lt;br /&gt; @connectors = [] # the svg path strings for all connectors in the tree &lt;br /&gt; @completed = {}&lt;br /&gt; @total_width = 0 # total width of drawing canvas adopted to tree size&lt;br /&gt; @total_height = @options[:box_height] + @options[:v_padding] # total height of drawing canvas adopted to tree size, initialised to height of one box                               &lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Return the Javascript required to draw the tree/forest at&lt;br /&gt;  # the top left of the specified dom element id&lt;br /&gt;  def draw(dom_element_id)&lt;br /&gt; calculate&lt;br /&gt; return generate_code(dom_element_id)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  protected&lt;br /&gt;&lt;br /&gt;  # Return the root for the specified node&lt;br /&gt;  def root_for(node)&lt;br /&gt; if @roots.member?(node)==true&lt;br /&gt;   return node&lt;br /&gt; end&lt;br /&gt; node.ancestors.each do |ancestor|&lt;br /&gt;   if @roots.member?(ancestor)==true&lt;br /&gt;  return ancestor&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Generate the SVG paths for connecting the specified node with its ancestor&lt;br /&gt;  def connect(node)&lt;br /&gt; offset = @options[:box_width]/2&lt;br /&gt; if node.children.size &gt; 0&lt;br /&gt;   @connectors &lt;&lt; "M#{@x_pos[node]+offset} #{@y_pos[node]+@options[:box_height]} L#{@x_pos[node]+offset} #{@y_pos[node]+@options[:box_height]+(@options[:v_padding]/2)}"&lt;br /&gt;   @connectors &lt;&lt; "M#{@x_pos[node.children.first]+offset} #{@y_pos[node]+@options[:box_height]+(@options[:v_padding]/2)} L#{@x_pos[node.children.last]+offset} #{@y_pos[node]+@options[:box_height]+(@options[:v_padding]/2)}"&lt;br /&gt; end&lt;br /&gt; node.children.each do |child|&lt;br /&gt;   @connectors &lt;&lt; "M#{@x_pos[child]+offset} #{@y_pos[child]-(@options[:v_padding]/2)} L#{@x_pos[child]+offset} #{@y_pos[child]}"&lt;br /&gt;   connect(child)&lt;br /&gt; end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Return the level of this node in the tree&lt;br /&gt;  def level(node)&lt;br /&gt; root = root_for(node)&lt;br /&gt; return (node.ancestors().size() - root.ancestors().size()) + 1&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Layer all nodes in the appropriate y-coordinates&lt;br /&gt;  def layer(node)&lt;br /&gt; l = level(node)&lt;br /&gt; @y_pos[node] = ((l - 1) * @options[:box_height]) + ((l - 1) * @options[:v_padding])&lt;br /&gt; if @y_pos[node] &gt; @total_height&lt;br /&gt;   @total_height = @y_pos[node]&lt;br /&gt; end&lt;br /&gt; node.children.each do |child|&lt;br /&gt;   layer(child)&lt;br /&gt; end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Return all nodes of the tree/forest on the specified level (regardless of whether these nodes are related)&lt;br /&gt;  def nodes_for_level(level, node = nil)&lt;br /&gt; nodes = []&lt;br /&gt; if (level == 1)&lt;br /&gt;   return @roots&lt;br /&gt; end&lt;br /&gt; if node == nil&lt;br /&gt;   @roots.each do |node|&lt;br /&gt;  nodes += nodes_for_level(level, node)&lt;br /&gt;   end&lt;br /&gt; else&lt;br /&gt;   if level==(level(node)+1)&lt;br /&gt;  nodes += node.children&lt;br /&gt;   else&lt;br /&gt;  node.children.each do |child|&lt;br /&gt;    nodes += nodes_for_level(level, child)&lt;br /&gt;  end&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt; return nodes&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Return the left sibling of this node (note that "sibling" does not mean that the two nodes&lt;br /&gt;  # belong to the same ancestor)&lt;br /&gt;  def left_sibling(node)&lt;br /&gt; siblings = nodes_for_level(level(node))&lt;br /&gt; if siblings&lt;br /&gt;   index = siblings.index(node)&lt;br /&gt;   if (index &gt; 0)&lt;br /&gt;  return siblings[index-1]&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt; return nil&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Shift the specified node with respect to its left sibling.&lt;br /&gt;  def shift_beyond(node)&lt;br /&gt; x = @x_pos[node]&lt;br /&gt; x += @x_mod[node]&lt;br /&gt; node.ancestors.each do |ancestor|&lt;br /&gt;   if @x_mod[ancestor]==nil&lt;br /&gt;  return x&lt;br /&gt;   else&lt;br /&gt;  x += @x_mod[ancestor]&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt; return x&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Position all nodes in the tree/forest&lt;br /&gt;  def calculate()&lt;br /&gt; @roots.each do |root|&lt;br /&gt;   first_pass(root)&lt;br /&gt;   second_pass(root)&lt;br /&gt;   layer(root)&lt;br /&gt;   connect(root)&lt;br /&gt;   if @total_width &lt; @max_width[root]&lt;br /&gt;  @total_width = @max_width[root]&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # First pass placing the nodes horizontally.&lt;br /&gt;  def first_pass(node)&lt;br /&gt; # first pass is post order, depth first&lt;br /&gt; node.children.each do |child|&lt;br /&gt;   first_pass(child)&lt;br /&gt; end&lt;br /&gt; mod = 0&lt;br /&gt; x_pos = 0&lt;br /&gt; children = node.children&lt;br /&gt; if (children.size == 0) # leaf node&lt;br /&gt;   left_sibling = left_sibling(node)&lt;br /&gt;   if left_sibling # leaf node with left sibling&lt;br /&gt;  x_pos = shift_beyond(left_sibling) + @options[:box_width] + @options[:h_padding]&lt;br /&gt;   end&lt;br /&gt; else # node with children&lt;br /&gt;   left_most_child = children.first&lt;br /&gt;   right_most_child = children.last&lt;br /&gt;   x_pos = (@x_pos[left_most_child] + @x_mod[left_most_child] + @x_pos[right_most_child] + @x_mod[right_most_child])/2&lt;br /&gt;   left_sibling = left_sibling(node)&lt;br /&gt;   if left_sibling # node with left sibling&lt;br /&gt;  mod = [(@options[:box_width] + @options[:h_padding]) - (x_pos - shift_beyond(left_sibling)), 0].max&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt; @x_pos[node] = x_pos&lt;br /&gt; @x_mod[node] = mod&lt;br /&gt; @x_cumul_mod[node] = 0&lt;br /&gt; root = root_for(node)&lt;br /&gt; @max_width[root] ||= 0&lt;br /&gt; if (@x_pos[node]+@options[:box_width]) &gt; @max_width[root]&lt;br /&gt;   @max_width[root] = @x_pos[node]+@options[:box_width]&lt;br /&gt; end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Second pass shifting the nodes horizontally according to the position of their left siblings.&lt;br /&gt;  def second_pass(node)&lt;br /&gt; @x_pos[node] +=  @x_mod[node] + @x_cumul_mod[node]&lt;br /&gt; node.children.each do |child|&lt;br /&gt;   @x_cumul_mod[child] = @x_mod[node] + @x_cumul_mod[node]&lt;br /&gt; end&lt;br /&gt; node.children.each do |child|&lt;br /&gt;   second_pass(child)&lt;br /&gt; end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Once the nodes have been positioned horizontally and vertically, generate the Raphael.js Javascript&lt;br /&gt;  # to draw the tree in the browser&lt;br /&gt;  def generate_code(dom_element_id)&lt;br /&gt; result = "&lt;script type='text/javascript' src='/javascripts/raphael-min.js'&gt;&lt;/script&gt;"&lt;br /&gt; result += "&lt;script type='text/javascript'&gt;"&lt;br /&gt; result += "var paper = Raphael(document.getElementById('#{dom_element_id}'), #{@total_width+@options[:h_padding]}, #{@total_height+@options[:box_height]+@options[:v_padding]});"&lt;br /&gt; result += "var t;"&lt;br /&gt; @roots.each do |root|&lt;br /&gt;   result += generate(root)&lt;br /&gt; end&lt;br /&gt; result += generate_connectors&lt;br /&gt; return result+"&lt;/script&gt;"&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Generate the box and text for a node.&lt;br /&gt;  def generate(node)&lt;br /&gt; result = generate_box(node)&lt;br /&gt; result += generate_text(node)&lt;br /&gt; node.children.each do |child|&lt;br /&gt;   result += generate(child)&lt;br /&gt; end&lt;br /&gt; return result&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Draw the box and corresponding mouseover (for domTT popups).&lt;br /&gt;  def generate_box(node)&lt;br /&gt; result = "t=paper.rect(#{@x_pos[node]}, #{@y_pos[node]}, #{@options[:box_width]}, #{@options[:box_height]}, #{@options[:box_round_edge]});"&lt;br /&gt; result += "t.attr({stroke:'#{@options[:box_color]}',fill:'#{@options[:box_color]}'});"&lt;br /&gt; result += "t.node.onmouseover=function(event){domTT_activate(this, event, 'content', '#{node.description.gsub(/\n/,"\\\n").gsub(/\r/,"\\\r")}', 'styleClass', 'tooltip')};"&lt;br /&gt; result += "t=paper.rect(#{@x_pos[node]+@options[:shadow_box_offset]}, #{@y_pos[node]+@options[:shadow_box_offset]}, #{@options[:box_width]}, #{@options[:box_height]}, #{@options[:box_round_edge]});"&lt;br /&gt; result += "t.attr({fill:'#{@options[:shadow_box_color]}',stroke:'#{@options[:shadow_box_color]}'});"&lt;br /&gt; result += "t.toBack();"&lt;br /&gt; return result&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Generate the node's text in the drawing as well as a link to displaying details about the node (if a link was specified upon construction) using node's id.&lt;br /&gt;  def generate_text(node)&lt;br /&gt; length = node.name.size&lt;br /&gt; result = "t=paper.text(#{@x_pos[node]}, #{@y_pos[node]}, '#{truncate(node.name, :length =&gt; @options[:max_text_length], :ommission =&gt; "...")}\\n(#{truncate((node.reference ? node.reference : ""), :length =&gt; @options[:max_text_length], :ommission =&gt; "...")})\\n#{truncate((node.person ? node.person.full_name : ""), :length =&gt; @options[:max_text_length], :ommission =&gt; "...")}');"&lt;br /&gt; result += "t.attr({'text-anchor': 'middle', 'font-size': #{@options[:font_size]}, 'font-weight': '#{@options[:font_weight]}', 'font-family': '#{@options[:font_family]}', fill:'#{@options[:text_color]}'});"&lt;br /&gt; result += "t.translate(((#{@options[:box_width]} - t.getBBox().width)/2)+t.getBBox().width/2, #{@options[:box_height]}/2);"&lt;br /&gt; if (@link)&lt;br /&gt;   result += "t.hover(function(){this.attr({fill: '#{@options[:highlight_color]}'});},function(){this.attr({fill: '#{@options[:text_color]}'});});"&lt;br /&gt;   result += "t.click(function(){location='#{@link}/#{node.id}'});"&lt;br /&gt; end&lt;br /&gt; return result;&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Generate the connecting lines between nodes.&lt;br /&gt;  def generate_connectors&lt;br /&gt; result ="t=paper.path('"&lt;br /&gt; @connectors.each do |path|&lt;br /&gt;   result += " #{path}"&lt;br /&gt; end&lt;br /&gt; result += "');t.attr({stroke:'#{@options[:line_color]}'});"&lt;br /&gt; return result&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Initialise the options using defaults or the overrides specified upon construction of this object &lt;br /&gt;  def init_options(options)&lt;br /&gt; @options = options&lt;br /&gt; @options[:box_width]  ||= 200 # width of a box representing a node&lt;br /&gt; @options[:box_height]  ||= 50 # height of a box representing a node&lt;br /&gt; @options[:tree_padding]  ||= 50 # horizontal padding between trees of the forest&lt;br /&gt; @options[:v_padding]  ||= 50 # vertical padding between all nodes of the forest&lt;br /&gt; @options[:h_padding]  ||= 40 # horizontal padding between nodes of the same tree in the forest&lt;br /&gt; @options[:box_round_edge] ||= 5 # radius of round edges (0 for normal edges)&lt;br /&gt; @options[:box_color]  ||= "#9aafe5" # fill color of a box representing a node&lt;br /&gt; @options[:highlight_color] ||= "#2e6ab1" # highlight color of the text if there's a link&lt;br /&gt; @options[:shadow_box_color] ||= "#0e509e" # color of the shadow for each box representing a node&lt;br /&gt; @options[:shadow_box_offset]||= 3 # offset of the shadow for each box representing a node&lt;br /&gt; @options[:line_color]  ||= "#0e509e" # for connectors between boxes representing nodes&lt;br /&gt; @options[:text_color]  ||= "#000" # color of text in each box representing a node&lt;br /&gt; @options[:font_size]  ||= 10&lt;br /&gt; @options[:font_weight]  ||= "normal"&lt;br /&gt; @options[:font_family]  ||= "Arial"&lt;br /&gt; @options[:max_text_length] ||= 35 # truncate any text in the box after the specified characters (appending '...')&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The simplest way to use this is in the view of a Rails application like so:&lt;br /&gt;&lt;br /&gt;&lt;pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"&gt;&lt;code&gt;&lt;br /&gt;      &lt;div id="org_chart_drawing" class="drawing"&gt;&lt;br /&gt;        &lt;%= TreeDraw.new([@organisation], url_for(:controller=&gt;"organisation", :action=&gt;"details", :id=&gt;nil)).draw("org_chart_drawing") %&gt;&lt;br /&gt;      &lt;/div&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Where &lt;tt&gt;@organisation&lt;/tt&gt; is the root of a tree (using &lt;tt&gt;acts_as_tree&lt;/tt&gt;), the specified URL provides the link to the details for a node and the string specified in the &lt;tt&gt;draw&lt;/tt&gt; method tells &lt;tt&gt;TreeDraw&lt;/tt&gt; to place the Raphael canvas in the element with ID &lt;tt&gt;org_chart_drawing&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;When drawing the box for a node, I am using the &lt;a href="http://www.mojavelinux.com/projects/domtooltip/"&gt;domTT&lt;/a&gt; tooltip to display a description of the detail for a node...&lt;br /&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8172024450972453037-2526942764277523231?l=mathiasrichter.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://mathiasrichter.blogspot.com/feeds/2526942764277523231/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8172024450972453037&amp;postID=2526942764277523231" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/2526942764277523231?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/2526942764277523231?v=2" /><link rel="alternate" type="text/html" href="http://mathiasrichter.blogspot.com/2010/08/drawing-trees-with-raphaeljs-and-ruby.html" title="Drawing trees with Raphael.js and Ruby" /><author><name>Mathias</name><uri>http://www.blogger.com/profile/13180258123551210286</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://3.bp.blogspot.com/_fNAEyWEDYJQ/Srfk1UYYVfI/AAAAAAAAAAM/qtiafPzZGZU/S220/Mathias.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;CUEBRHc-fyp7ImA9WxFRF04.&quot;"><id>tag:blogger.com,1999:blog-8172024450972453037.post-8736393068338424516</id><published>2010-05-01T18:56:00.011+02:00</published><updated>2010-05-01T19:07:35.957+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-05-01T19:07:35.957+02:00</app:edited><title /><content type="html">If you are using Ferret for Rails full text search, never forget to do the following:&lt;br /&gt;&lt;br/&gt;&lt;tt&gt;gem install acts_as_ferret&lt;/tt&gt; (duh!)&lt;br/&gt;&lt;br /&gt;If this works with your Rails install - fine. Don't go on and change a winning team. If it doesn't work (e.g. when you do a &lt;tt&gt;rake db:migrate&lt;/tt&gt; and you get &lt;br /&gt;&lt;br/&gt;&lt;tt&gt;no such file to load - ferret&lt;/tt&gt;&lt;/br&gt; &lt;br /&gt;&lt;br /&gt;do the following&lt;br /&gt;&lt;br/&gt;&lt;tt&gt;gem install ferret&lt;/tt&gt;&lt;br/&gt; &lt;br /&gt;and you should be fine.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8172024450972453037-8736393068338424516?l=mathiasrichter.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://mathiasrichter.blogspot.com/feeds/8736393068338424516/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8172024450972453037&amp;postID=8736393068338424516" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/8736393068338424516?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/8736393068338424516?v=2" /><link rel="alternate" type="text/html" href="http://mathiasrichter.blogspot.com/2010/05/if-you-are-using-ferret-for-rails-full.html" title="" /><author><name>Mathias</name><uri>http://www.blogger.com/profile/13180258123551210286</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://3.bp.blogspot.com/_fNAEyWEDYJQ/Srfk1UYYVfI/AAAAAAAAAAM/qtiafPzZGZU/S220/Mathias.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CEIEQHk4eip7ImA9WxBVFEU.&quot;"><id>tag:blogger.com,1999:blog-8172024450972453037.post-1310429423070168608</id><published>2010-02-18T09:24:00.003+01:00</published><updated>2010-02-18T09:41:41.732+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-02-18T09:41:41.732+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ruby rails" /><title>Rendering XML with Rails</title><content type="html">I had to code a webservice with Rails yesterday. Its obvious purpose was to respond with XML formatted data to an HTTP GET request. I looked at some of the examples out there and ran into trouble. I lost some time searching for a solution, so here's the summary of the solution to save others the hassle.&lt;br /&gt;&lt;br /&gt;Here's what people recommend doing to render XML from a HTTP request in Rails:&lt;br /&gt;&lt;br /&gt;&lt;pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"&gt;&lt;code&gt;&lt;br /&gt;class WebServiceController &lt; ApplicationController&lt;br /&gt;&lt;br /&gt; def people&lt;br /&gt;  @people = Person.find :all&lt;br /&gt;  respond do |format|&lt;br /&gt;   format.xml&lt;br /&gt;  end&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And the corresponding &lt;code&gt;people.xml.builder&lt;/code&gt; in &lt;code&gt;/views/web_service/&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"&gt;&lt;code&gt;&lt;br /&gt;xml.instruct! :xml, :version=&gt;"1.0"&lt;br /&gt;&lt;br /&gt;xml.people do&lt;br /&gt;  @people.each do |person|&lt;br /&gt;    xml.person do&lt;br /&gt;        xml.firstname person.first_name&lt;br /&gt;        xml.lastname person.last_name&lt;br /&gt;        xml.age person.age&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;When testing this, I kept getting browser errors about invalid XML. When I looked at the data that was returned from my webservice call, I realised that the XML was generated like I wanted, but it was subsequently embedded into the application layout I am using for all of the views (&lt;code&gt;views/layouts/application.rhtml&lt;/code&gt;).&lt;br /&gt;&lt;br /&gt;The issue was resolved by changing my controller code to&lt;br /&gt;&lt;br /&gt;&lt;pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"&gt;&lt;code&gt;&lt;br /&gt;class WebServiceController &lt; ApplicationController&lt;br /&gt;&lt;br /&gt; def people&lt;br /&gt;  @people = Person.find :all&lt;br /&gt;  render :template =&gt; 'web_service/people.xml.builder', :layout =&gt; false&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8172024450972453037-1310429423070168608?l=mathiasrichter.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://mathiasrichter.blogspot.com/feeds/1310429423070168608/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8172024450972453037&amp;postID=1310429423070168608" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/1310429423070168608?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/1310429423070168608?v=2" /><link rel="alternate" type="text/html" href="http://mathiasrichter.blogspot.com/2010/02/rendering-xml-with-rails.html" title="Rendering XML with Rails" /><author><name>Mathias</name><uri>http://www.blogger.com/profile/13180258123551210286</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://3.bp.blogspot.com/_fNAEyWEDYJQ/Srfk1UYYVfI/AAAAAAAAAAM/qtiafPzZGZU/S220/Mathias.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;A0cFQ38yeSp7ImA9WxNVEUQ.&quot;"><id>tag:blogger.com,1999:blog-8172024450972453037.post-4311033988075698123</id><published>2009-10-21T16:32:00.011+02:00</published><updated>2009-10-22T10:10:12.191+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-22T10:10:12.191+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="MySql" /><category scheme="http://www.blogger.com/atom/ns#" term="UTF-8" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="character encoding" /><category scheme="http://www.blogger.com/atom/ns#" term="UTF8" /><category scheme="http://www.blogger.com/atom/ns#" term="Spring" /><category scheme="http://www.blogger.com/atom/ns#" term="Tomcat" /><category scheme="http://www.blogger.com/atom/ns#" term="JPA" /><title>Character Encoding UTF-8 with JPA/Hibernate, MySql and Tomcat</title><content type="html">I'm writing a little Java application using JPA (Hibernate implementation) and Spring. The application will run on Tomcat and uses MySql as the RDBMS.&lt;br /&gt;&lt;br /&gt;The problem I had today was with the good old character encoding: I was able to store German Umlaut characters (üöä) properly in MySql, but whenever I retrieved them, they would be scrambled - regardless of whether I displayed the result on a web page or just printed it to the console.&lt;br /&gt;&lt;br /&gt;So, the problem is: how to consistently set UTF-8 as the character encoding of choice throughout the whole stack:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;for MySql as well as for any session coming through the JDBC driver in order to ensure that any entity created by Hibernate/JPA uses UTF-8&lt;br /&gt;&lt;/li&gt;&lt;li&gt;for Tomcat to make sure that any data served uses UTF-8&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;I know that you'll find a lot of material on the solution for each individual piece of software in my tech stack across the net. However, I still think it's worth to post this solution as I did not find all elements of it in one place (and don't want to search again the next time :-)&lt;br /&gt;&lt;br /&gt;Here's what I did:&lt;br /&gt;&lt;br /&gt;1. Ensure that MySql runs on UTF-8 as default: in the MySql configuration file &lt;span style="font-family:courier new;"&gt;my.cnf &lt;/span&gt;add the following in the section for &lt;span style="font-family:courier new;"&gt;mysqld&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;[mysqld]&lt;br /&gt;...&lt;br /&gt;default-character-set=utf8&lt;br /&gt;default-collation=utf8_general_ci&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;2. Configure your MySql JDBC driver connection as follows (obviously hostname, port and schema are probably different in your configuration :-):&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;jdbc:mysql://localhost:3306/test?useUnicode=true&amp;amp;connectionCollation=utf8_general_ci&amp;amp;characterSetResults=utf8&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;When configuring the above driver URL in your Spring XML context definition, don't forget to escape the Ampersand as you will get parsing errors otherwise.&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&lt;/span&gt;3. Configure Tomcat for UTF-8 by adding the following line to your : &lt;span style="font-family:courier new;"&gt;catalina.bat &lt;/span&gt;or &lt;span style="font-family:courier new;"&gt;catalina.sh:&lt;br /&gt;&lt;br /&gt;JAVA_OPTS="$JAVA_OPTS -Djavax.servlet.request.encoding=UTF-8 -Dfile.encoding=UTF-8"&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;Versions I am using: MySql 5.0, Tomcat 6.0.20, Spring 2.5.6, Java 6, MySql Connector 5.1.6&lt;br /&gt;&lt;br /&gt;Happy hacking!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8172024450972453037-4311033988075698123?l=mathiasrichter.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://mathiasrichter.blogspot.com/feeds/4311033988075698123/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8172024450972453037&amp;postID=4311033988075698123" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/4311033988075698123?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/4311033988075698123?v=2" /><link rel="alternate" type="text/html" href="http://mathiasrichter.blogspot.com/2009/10/character-encoding-utf-8-with.html" title="Character Encoding UTF-8 with JPA/Hibernate, MySql and Tomcat" /><author><name>Mathias</name><uri>http://www.blogger.com/profile/13180258123551210286</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://3.bp.blogspot.com/_fNAEyWEDYJQ/Srfk1UYYVfI/AAAAAAAAAAM/qtiafPzZGZU/S220/Mathias.jpg" /></author><thr:total>6</thr:total></entry><entry gd:etag="W/&quot;A08AQXY9cCp7ImA9WxVSF0Q.&quot;"><id>tag:blogger.com,1999:blog-8172024450972453037.post-5236488972739553827</id><published>2009-01-12T23:29:00.002+01:00</published><updated>2009-01-12T23:37:20.868+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-12T23:37:20.868+01:00</app:edited><title /><content type="html">Been a long time since I have posted anything. Got dragged into some, well - let's say, interesting work in my day job. Done some programming anyway.&lt;br /&gt;&lt;br /&gt;GWT-Ext or SmartClient based GUI wiht a JPA/Spring/MySQL/JAX-B/JAX-RS driven backend. And I'm using Envers for auditing - I love it.&lt;br /&gt;&lt;br /&gt;Got fed up writing all those REST requests, so basically just set up a service with a Groovy engine behind. This allows me to just send scripts using my plain POJO domain model from the GUI without having to expose every little piece of functionality as a REST service. All the while every script execution returns a piece of XML driving the AJAX GUI. On this front I love the store-based model of SmartClient over the store based model of GWT-Ext (because of the CRUD service hooks).&lt;br /&gt;&lt;br /&gt;Need to play around with the Groovy bit some more - but I can almost hear the critics yell "SECURITY ISSUE"!&lt;br /&gt;&lt;br /&gt;Protecting the whole thing with CAS and aspect-driven security on the server side for method execution as well as data retrievals/updates using JPA.&lt;br /&gt;&lt;br /&gt;Need to play with this a bit more and will provide some code here. Watch this space...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8172024450972453037-5236488972739553827?l=mathiasrichter.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://mathiasrichter.blogspot.com/feeds/5236488972739553827/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8172024450972453037&amp;postID=5236488972739553827" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/5236488972739553827?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/5236488972739553827?v=2" /><link rel="alternate" type="text/html" href="http://mathiasrichter.blogspot.com/2009/01/been-long-time-since-i-have-posted.html" title="" /><author><name>Mathias</name><uri>http://www.blogger.com/profile/13180258123551210286</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://3.bp.blogspot.com/_fNAEyWEDYJQ/Srfk1UYYVfI/AAAAAAAAAAM/qtiafPzZGZU/S220/Mathias.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CEAHR3s9eSp7ImA9WxZaFU0.&quot;"><id>tag:blogger.com,1999:blog-8172024450972453037.post-8613113254750389996</id><published>2008-04-28T21:11:00.004+02:00</published><updated>2008-04-29T22:58:56.561+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-29T22:58:56.561+02:00</app:edited><title>Creating OSGi bundles from any JAR</title><content type="html">&lt;span style="font-size:85%;"&gt;One of the major pains in OSGi is the creation of bundles. Peter Kriens' &lt;a href="http://www.aqute.biz/Code/Bnd"&gt;BND &lt;/a&gt;tool does a great job - it's powerful and can be used for creating the most complex OSGi bundles.&lt;br /&gt;&lt;br /&gt;But haven't you ever wished for a simple tool at which you just throw an existing JAR file and it produces the corresponding OSGi bundle jar? Well, I have - and I have written the class below which does exactly that. And, yes, I have been found guilty of reinventing the wheel (see comment below) - a simple wrapper to BND would have achieved the same purpose except I learnt a few things along the way doing it myself :-)&lt;br /&gt;&lt;br /&gt;This is a convenient way to quickly produce an OSGi bundle to be deployed in your OSGi runtime.&lt;br /&gt;&lt;br /&gt;Clearly, this is not a replacement for BND or similar tools. It is a good 80% solution. Hopefully, we will soon see reasonably searchable OSGi bundle repositories come up (and I do not mean what is currently offered on the OSGi alliance site - you can't even search for bundles offering a certain class or package, to name just one use case). Hopefully, we will also see bundles offered for all of the major open source packages, not just a chosen few used in Eclipse. Ok, rant finished.&lt;br /&gt;&lt;br /&gt;Here comes the code. [Deleted my comment about reinventing the weel from the original post - admittedly I have been found guilty].&lt;br /&gt;&lt;br /&gt;And before I forget, this code depends on the following JARs:&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;&lt;a href="http://vafer.org/projects/dependency/"&gt;Dependency&lt;/a&gt;: I had to adapt it to the current version of ASM (that was simple, the source code is available). If you send me an email, I can send you my version of the code.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;&lt;a href="http://asm.objectweb.org/"&gt;ASM&lt;/a&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;&lt;a href="http://commons.apache.org/"&gt;Apache Commons IO&lt;/a&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-size:85%;"&gt;&lt;br /&gt;&lt;pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"&gt;&lt;code&gt;import java.io.BufferedReader;&lt;br /&gt;import java.io.File;&lt;br /&gt;import java.io.FileInputStream;&lt;br /&gt;import java.io.FileNotFoundException;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.InputStream;&lt;br /&gt;import java.io.InputStreamReader;&lt;br /&gt;import java.util.Enumeration;&lt;br /&gt;import java.util.HashMap;&lt;br /&gt;import java.util.Set;&lt;br /&gt;import java.util.StringTokenizer;&lt;br /&gt;import java.util.jar.JarEntry;&lt;br /&gt;import java.util.jar.JarFile;&lt;br /&gt;import org.vafer.dependency.utils.DependencyUtils;&lt;br /&gt;import org.apache.commons.io.FileUtils;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* This class implements a simplistic approach to creating&lt;br /&gt;* bundles from any JAR file.&lt;br /&gt;*&lt;br /&gt;* @author Mathias Richter&lt;br /&gt;*/&lt;br /&gt;public class Osgify&lt;br /&gt;{&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Return all packages imported by classes in the specified jar file.&lt;br /&gt;   *&lt;br /&gt;   * @param jarFileName The name of the JAR file to analyse.&lt;br /&gt;   * @return A set of package names.&lt;br /&gt;   *&lt;br /&gt;   * @throws java.io.FileNotFoundException&lt;br /&gt;   * @throws java.io.IOException&lt;br /&gt;   */&lt;br /&gt;  public static Set&amp;lt;String&amp;gt; getImportPackages( String jarFileName ) throws FileNotFoundException, IOException&lt;br /&gt;  {&lt;br /&gt;      HashMap&amp;lt;String, String&amp;gt; packages = new HashMap&amp;lt;String, String&amp;gt;();&lt;br /&gt;      Set&amp;lt;String&amp;gt; dependencies = DependencyUtils.getDependenciesOfJar( new FileInputStream( jarFileName ) );&lt;br /&gt;      for ( String name : dependencies )&lt;br /&gt;      {&lt;br /&gt;          if ( !name.startsWith( "[" ) )&lt;br /&gt;          {&lt;br /&gt;              int index = name.lastIndexOf( "." );&lt;br /&gt;              if ( index &amp;gt; 0 )&lt;br /&gt;                  name = name.substring( 0, index );&lt;br /&gt;          } else&lt;br /&gt;          {&lt;br /&gt;              if ( name.length() &amp;gt; 2 )&lt;br /&gt;              {&lt;br /&gt;                  int index = name.lastIndexOf( "." );&lt;br /&gt;                  if ( index &amp;gt; 2 )&lt;br /&gt;                      name = name.substring( 2, index );&lt;br /&gt;              } else&lt;br /&gt;                  continue;&lt;br /&gt;          }&lt;br /&gt;          if ( !name.startsWith( "java." ) )&lt;br /&gt;              packages.put( name, name );&lt;br /&gt;      }&lt;br /&gt;      return packages.keySet();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Return a set of names of the packages declared in the specified JAR file.&lt;br /&gt;   *&lt;br /&gt;   * @param jarFileName The name of the JAR file to analyse.&lt;br /&gt;   * @return The names of the packages declared by the classes in the JAR file.&lt;br /&gt;   *&lt;br /&gt;   * @throws java.io.IOException&lt;br /&gt;   */&lt;br /&gt;  public static Set&amp;lt;String&amp;gt; getExportPackages( String jarFileName ) throws IOException&lt;br /&gt;  {&lt;br /&gt;      HashMap&amp;lt;String, String&amp;gt; packages = new HashMap&amp;lt;String, String&amp;gt;();&lt;br /&gt;      JarFile file = new JarFile( jarFileName );&lt;br /&gt;      for ( Enumeration&amp;lt;JarEntry&amp;gt; e = file.entries(); e.hasMoreElements();)&lt;br /&gt;      {&lt;br /&gt;          String name = e.nextElement().getName();&lt;br /&gt;          if ( name.endsWith( ".class" ) )&lt;br /&gt;          {&lt;br /&gt;              name = name.replaceAll( "/", "." );&lt;br /&gt;              int index = name.lastIndexOf( "." );&lt;br /&gt;              if ( index &amp;gt; 0 )&lt;br /&gt;                  name = name.substring( 0, name.lastIndexOf( "." ) );&lt;br /&gt;              index = name.lastIndexOf( "." );&lt;br /&gt;              if ( index &amp;gt; 0 )&lt;br /&gt;                  name = name.substring( 0, name.lastIndexOf( "." ) );&lt;br /&gt;              packages.put( name, name );&lt;br /&gt;          }&lt;br /&gt;      }&lt;br /&gt;      return packages.keySet();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Analyse the specified JAR for its package imports and package declarations and&lt;br /&gt;   * create a manifest file describing these in OSGi fashion.&lt;br /&gt;   *&lt;br /&gt;   * @param jarFileName The name of the JAR file for which to create an OSGi bundle manifest. May not be null.&lt;br /&gt;   * @param bundleName The name of the bundle to delcrae in the manifest. May not be null.&lt;br /&gt;   * @param bundleSymbolicName The symbolic name to declare in the manifest. If null, uses the bundle name.&lt;br /&gt;   * @param bundleVersion The version to declare in the manifest. If null, uses "1.0.0".&lt;br /&gt;   * @param bundleActivator The activator class to declare in the manifest. If null, does not specify an activator.&lt;br /&gt;   * @param resolveOptional If true, all package imports will be marked with "resolution:=optional".&lt;br /&gt;   *&lt;br /&gt;   * @return The manifest contents as a string.&lt;br /&gt;   *&lt;br /&gt;   * @throws java.io.FileNotFoundException&lt;br /&gt;   * @throws java.io.IOException&lt;br /&gt;   */&lt;br /&gt;  public static String getManifest( String jarFileName, String bundleName, String bundleSymbolicName, String bundleVersion, String bundleActivator, boolean resolveOptional ) throws FileNotFoundException, IOException&lt;br /&gt;  {&lt;br /&gt;      StringBuffer manifest = new StringBuffer();&lt;br /&gt;      Set&amp;lt;String&amp;gt; im = getImportPackages( jarFileName );&lt;br /&gt;      Set&amp;lt;String&amp;gt; ex = getExportPackages( jarFileName );&lt;br /&gt;      im.removeAll( ex );&lt;br /&gt;      manifest.append( "Bundle-Name: " );&lt;br /&gt;      manifest.append( bundleName );&lt;br /&gt;      manifest.append( "\n" );&lt;br /&gt;      if ( bundleSymbolicName == null )&lt;br /&gt;          bundleSymbolicName = bundleName;&lt;br /&gt;      manifest.append( "Bundle-SymbolicName: " );&lt;br /&gt;      manifest.append( bundleSymbolicName );&lt;br /&gt;      manifest.append( "\n" );&lt;br /&gt;      if ( bundleVersion == null )&lt;br /&gt;          bundleVersion = "1.0.0";&lt;br /&gt;      manifest.append( "Bundle-Version: " );&lt;br /&gt;      manifest.append( bundleVersion );&lt;br /&gt;      manifest.append( "\n" );&lt;br /&gt;      if ( bundleActivator != null )&lt;br /&gt;      {&lt;br /&gt;          manifest.append( "Bundle-Activator: " );&lt;br /&gt;          manifest.append( bundleActivator );&lt;br /&gt;          manifest.append( "\n" );&lt;br /&gt;      }&lt;br /&gt;      manifest.append( "Import-Package: " );&lt;br /&gt;      boolean previous = false;&lt;br /&gt;      for ( String p : im )&lt;br /&gt;      {&lt;br /&gt;          if ( previous )&lt;br /&gt;              manifest.append( "," );&lt;br /&gt;          manifest.append( p );&lt;br /&gt;          if ( resolveOptional )&lt;br /&gt;              manifest.append( ";resolution:=optional" );&lt;br /&gt;          previous = true;&lt;br /&gt;      }&lt;br /&gt;      manifest.append( "\n" );&lt;br /&gt;      manifest.append( "Export-Package: " );&lt;br /&gt;      previous = false;&lt;br /&gt;      for ( String p : ex )&lt;br /&gt;      {&lt;br /&gt;          if ( previous )&lt;br /&gt;              manifest.append( "," );&lt;br /&gt;          manifest.append( p );&lt;br /&gt;          previous = true;&lt;br /&gt;      }&lt;br /&gt;      manifest.append( "\n" );&lt;br /&gt;      manifest.append( "\n" );&lt;br /&gt;      return split( manifest.toString(), 70 );&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Format the specified source string to use a max line length as specified.&lt;br /&gt;   * Lines will be broken in "manifest" fashion, ie. continuation lines start with&lt;br /&gt;   * a single space.&lt;br /&gt;   *&lt;br /&gt;   * @param source the source String to format&lt;br /&gt;   * @param maxLineLength The maximum line length in the result.&lt;br /&gt;   * @return The formatted string.&lt;br /&gt;   */&lt;br /&gt;  protected static String split( String source, int maxLineLength )&lt;br /&gt;  {&lt;br /&gt;      StringBuffer result = new StringBuffer();&lt;br /&gt;      StringTokenizer tokens = new StringTokenizer( source, "\n" );&lt;br /&gt;      while ( tokens.hasMoreTokens() )&lt;br /&gt;      {&lt;br /&gt;          String line = tokens.nextToken();&lt;br /&gt;          int length = line.length();&lt;br /&gt;          for ( int index = 0; index &amp;lt; length;)&lt;br /&gt;          {&lt;br /&gt;              if ( index &amp;gt; 0 )&lt;br /&gt;                  result.append( " " );&lt;br /&gt;              int remainder = length - index;&lt;br /&gt;              if ( remainder &amp;lt;= maxLineLength )&lt;br /&gt;              {&lt;br /&gt;                  result.append( line.substring( index, length ) );&lt;br /&gt;                  index = length;&lt;br /&gt;              } else&lt;br /&gt;              {&lt;br /&gt;                  result.append( line.substring( index, index + maxLineLength ) );&lt;br /&gt;                  index += maxLineLength;&lt;br /&gt;              }&lt;br /&gt;              result.append( "\n" );&lt;br /&gt;          }&lt;br /&gt;      }&lt;br /&gt;      return result.toString();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * If the process has output, return it as a String.&lt;br /&gt;   *&lt;br /&gt;   * @param process The process.&lt;br /&gt;   * @return The process output as String.&lt;br /&gt;   * @throws java.io.IOException&lt;br /&gt;   */&lt;br /&gt;  protected static String getOutput( Process process ) throws IOException&lt;br /&gt;  {&lt;br /&gt;      StringBuffer result = new StringBuffer();&lt;br /&gt;      InputStream is = process.getInputStream();&lt;br /&gt;      InputStreamReader isr = new InputStreamReader( is );&lt;br /&gt;      BufferedReader br = new BufferedReader( isr );&lt;br /&gt;      String line = null;&lt;br /&gt;      while ( ( line = br.readLine() ) != null )&lt;br /&gt;          result.append( line );&lt;br /&gt;      is.close();&lt;br /&gt;      isr.close();&lt;br /&gt;      br.close();&lt;br /&gt;      return result.toString();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Create an OSGi jar from the specified JAR file using the specified bundle name.&lt;br /&gt;   * The OSGi bundle jar will be created in the same directory as the original JAR.&lt;br /&gt;   *&lt;br /&gt;   * @param jarFileName The JAR file to analyse and produce an OSGi bundle from.&lt;br /&gt;   * @param bundleName the name of the bundle.&lt;br /&gt;   *&lt;br /&gt;   * @throws java.io.FileNotFoundException&lt;br /&gt;   * @throws java.io.IOException&lt;br /&gt;   * @throws java.lang.InterruptedException&lt;br /&gt;   */&lt;br /&gt;  public static void osgify( String jarFileName, String bundleName ) throws FileNotFoundException, IOException, InterruptedException&lt;br /&gt;  {&lt;br /&gt;      osgify( jarFileName, bundleName, null, null, null, true );&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Create an OSGi jar from the specified JAR file using the specified bundle name and bundle symbolic name.&lt;br /&gt;   * The OSGi bundle jar will be created in the same directory as the original JAR.&lt;br /&gt;   *&lt;br /&gt;   * @param jarFileName The JAR file to analyse and produce an OSGi bundle from.&lt;br /&gt;   * @param bundleName the name of the bundle.&lt;br /&gt;   * @param bundleSymbolicName The symbolic bundle name to use.&lt;br /&gt;   *&lt;br /&gt;   * @throws java.io.FileNotFoundException&lt;br /&gt;   * @throws java.io.IOException&lt;br /&gt;   * @throws java.lang.InterruptedException&lt;br /&gt;   */&lt;br /&gt;  public static void osgify( String jarFileName, String bundleName, String bundleSymbolicName ) throws FileNotFoundException, IOException, InterruptedException&lt;br /&gt;  {&lt;br /&gt;      osgify( jarFileName, bundleName, bundleSymbolicName, null, null, true );&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Create an OSGi jar from the specified JAR file using the specified bundle name and bundle symbolic name.&lt;br /&gt;   * The OSGi bundle jar will be created in the same directory as the original JAR.     *&lt;br /&gt;   *&lt;br /&gt;   * @param jarFileName The name of the JAR file for which to create an OSGi bundle manifest. May not be null.&lt;br /&gt;   * @param bundleName The name of the bundle to declare in the manifest. May not be null.&lt;br /&gt;   * @param bundleSymbolicName The symbolic name to declare in the manifest. If null, uses the bundle name.&lt;br /&gt;   * @param bundleVersion The version to declare in the manifest. If null, uses "1.0.0".&lt;br /&gt;   * @param bundleActivator The activator class to declare in the manifest. If null, does not specify an activator.&lt;br /&gt;   * @param resolveOptional If true, all package imports will be marked with "resolution:=optional".&lt;br /&gt;   *&lt;br /&gt;   * @throws java.io.FileNotFoundException&lt;br /&gt;   * @throws java.io.IOException&lt;br /&gt;   * @throws java.lang.InterruptedException&lt;br /&gt;   */&lt;br /&gt;  public static void osgify( String jarFileName, String bundleName, String bundleSymbolicName, String bundleVersion, String bundleActivator, boolean resolveOptional ) throws FileNotFoundException, IOException, InterruptedException&lt;br /&gt;  {&lt;br /&gt;      int index = jarFileName.lastIndexOf( File.separator );&lt;br /&gt;      String targetDir = "./";&lt;br /&gt;      if ( index &amp;gt; 0 )&lt;br /&gt;          targetDir = jarFileName.substring( 0, index + 1 );&lt;br /&gt;      File tempDir = new File( targetDir + ".osgify" + System.currentTimeMillis() );&lt;br /&gt;      FileUtils.forceMkdir( tempDir );&lt;br /&gt;      try&lt;br /&gt;      {&lt;br /&gt;          Process p = Runtime.getRuntime().exec( "jar xf " + jarFileName, null, tempDir );&lt;br /&gt;          if ( p.waitFor() != 0 )&lt;br /&gt;              throw new Error( "Could not expand '" + jarFileName + "' in temporary directory: "  + getOutput( p ) );&lt;br /&gt;          File manifest = new File( tempDir.getAbsolutePath() + "/META-INF/MANIFEST.MF" );&lt;br /&gt;          if ( manifest.exists() )&lt;br /&gt;              FileUtils.forceDelete( manifest );&lt;br /&gt;          manifest = new File( tempDir.getAbsolutePath() + "/META-INF/MANIFEST.MF" );&lt;br /&gt;          String manifestContent = getManifest( jarFileName, bundleName, bundleSymbolicName, bundleVersion, bundleActivator, resolveOptional );&lt;br /&gt;          FileUtils.writeStringToFile( manifest, manifestContent );&lt;br /&gt;          String netJarName = jarFileName.substring( jarFileName.lastIndexOf( File.separator ) + 1, jarFileName.lastIndexOf( "." ) );&lt;br /&gt;          String targetJarName = targetDir + netJarName + "_" + bundleVersion + ".jar";&lt;br /&gt;          String cmd = "jar cfm " + targetJarName + " META-INF/MANIFEST.MF ./*";&lt;br /&gt;          p = Runtime.getRuntime().exec( cmd, null, tempDir );&lt;br /&gt;          if ( p.waitFor() != 0 )&lt;br /&gt;              throw new Error( "Could not create OSGi bundle jar '" + targetJarName + "' in temporary directory: " + getOutput( p )  );&lt;br /&gt;      } finally&lt;br /&gt;      {&lt;br /&gt;          FileUtils.forceDelete( tempDir );&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8172024450972453037-8613113254750389996?l=mathiasrichter.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://mathiasrichter.blogspot.com/feeds/8613113254750389996/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8172024450972453037&amp;postID=8613113254750389996" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/8613113254750389996?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/8613113254750389996?v=2" /><link rel="alternate" type="text/html" href="http://mathiasrichter.blogspot.com/2008/04/creating-osgi-bundles-from-any-jar.html" title="Creating OSGi bundles from any JAR" /><author><name>Mathias</name><uri>http://www.blogger.com/profile/13180258123551210286</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://3.bp.blogspot.com/_fNAEyWEDYJQ/Srfk1UYYVfI/AAAAAAAAAAM/qtiafPzZGZU/S220/Mathias.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;AkQDQ3YyfCp7ImA9WxZbEkU.&quot;"><id>tag:blogger.com,1999:blog-8172024450972453037.post-2987552901478650279</id><published>2008-04-15T21:28:00.004+02:00</published><updated>2008-04-15T21:39:32.894+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-15T21:39:32.894+02:00</app:edited><title>Standalone Java CAS Client</title><content type="html">&lt;span style="font-size:85%;"&gt;There's a variety of clients for CAS. The Java-based clients (JA-SIG, Yale, see &lt;a href="http://www.ja-sig.org/"&gt;JA-SIG website&lt;/a&gt;) typically handle the browser-based client interaction with CAS very well through ServletFilter implementations.&lt;br /&gt;&lt;br /&gt;Now what about programmatic authentication, i.e. achieving authentication through non-browser based applications? There exists a &lt;a href="http://www.ja-sig.org/wiki/display/CASC/.Net+Cas+Client"&gt;CAS .NET client&lt;/a&gt; but I did not manage to find the appropriate Java implementation. So here goes - it is based on the Apache HttpClient.&lt;br /&gt;&lt;br /&gt;In case I missed any existing implementation achieving the same purpose, let's look at the bright side: at least now I understand the CAS protocol :-)&lt;br /&gt;&lt;br /&gt;My CAS client works within any application. It uses the HttpClient and behaves like a browser client as CAS requires cookie support.&lt;br /&gt;&lt;br /&gt;Here's the code:&lt;br /&gt;&lt;br /&gt;&lt;pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"&gt;&lt;code&gt;&lt;br /&gt;import org.apache.commons.httpclient.Header;&lt;br /&gt;import org.apache.commons.httpclient.HttpClient;&lt;br /&gt;import org.apache.commons.httpclient.HttpMethod;&lt;br /&gt;import org.apache.commons.httpclient.HttpStatus;&lt;br /&gt;import org.apache.commons.httpclient.methods.GetMethod;&lt;br /&gt;import org.apache.commons.httpclient.methods.PostMethod;&lt;br /&gt;import org.apache.log4j.Logger;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* The CasClient allows users to programmatically login&lt;br /&gt;* to CAS protected services based on the CAS 2 protocol.&lt;br /&gt;* This client behaves like a browser-client in terms of&lt;br /&gt;* cookie handling.&amp;lt;br&amp;gt;&lt;br /&gt;*&lt;br /&gt;* @author Mathias Richter&lt;br /&gt;*/&lt;br /&gt;public class CasClient&lt;br /&gt;{&lt;br /&gt;  &lt;br /&gt;   public static Logger LOG = Logger.getLogger( CasClient.class  );&lt;br /&gt;&lt;br /&gt;   public static final String LOGIN_URL_PART = "login";&lt;br /&gt;   public static final String SERVICE_VALIDATE_URL_PART = "serviceValidate";&lt;br /&gt;   public static final String TICKET_BEGIN = "ticket=";&lt;br /&gt;   private static final String LT_BEGIN = "name=\"lt\" value=\"";&lt;br /&gt;   public static final String CAS_USER_BEGIN = "&amp;lt;cas:user&amp;gt;";&lt;br /&gt;   public static final String CAS_USER_END = "&amp;lt;/cas:user&amp;gt;";&lt;br /&gt;  &lt;br /&gt;   private HttpClient fClient;&lt;br /&gt;   private String fCasUrl;&lt;br /&gt;  &lt;br /&gt;   /**&lt;br /&gt;    * Construct a new CasClient.&lt;br /&gt;    *&lt;br /&gt;    * @param casUrl The base URL of the CAS service to be used.&lt;br /&gt;    */&lt;br /&gt;   public CasClient( String casBaseUrl )&lt;br /&gt;   {&lt;br /&gt;       this( new HttpClient(), casBaseUrl );&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;   /**&lt;br /&gt;    * Construct a new CasClient which uses the specified HttpClient&lt;br /&gt;    * for its HTTP calls.&lt;br /&gt;    *&lt;br /&gt;    * @param client&lt;br /&gt;    * @param casBaseUrl&lt;br /&gt;    */&lt;br /&gt;   public CasClient( HttpClient client, String casBaseUrl )&lt;br /&gt;   {&lt;br /&gt;       fClient = client;&lt;br /&gt;       fCasUrl = casBaseUrl;&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;   /**&lt;br /&gt;    * Authenticate the specified username with the specified password.&lt;br /&gt;    * This will not yield any ticket, as no service is authenticated&lt;br /&gt;    * against. This wil just set the CAS cookie in this client upon&lt;br /&gt;    * successful authentication.&lt;br /&gt;    *&lt;br /&gt;    * @param username&lt;br /&gt;    * @param password&lt;br /&gt;    */&lt;br /&gt;   public void authenticate( String username, String password )&lt;br /&gt;   {&lt;br /&gt;       authenticate( null, username, password );&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;   /**&lt;br /&gt;    * Validate the specified service ticket against the specified service.&lt;br /&gt;    * If the ticket is valid, this will yield the clear text user name&lt;br /&gt;    * of the autenticated user.&amp;lt;br&amp;gt;&lt;br /&gt;    * Note that each service ticket issued by CAS can be used exactly once&lt;br /&gt;    * to validate.&lt;br /&gt;    *&lt;br /&gt;    * @param serviceUrl&lt;br /&gt;    * @param serviceTicket&lt;br /&gt;    *&lt;br /&gt;    * @return Clear text username of the authenticated user.&lt;br /&gt;    */&lt;br /&gt;   public String validate( String serviceUrl, String serviceTicket )&lt;br /&gt;   {&lt;br /&gt;       String result = null;&lt;br /&gt;       PostMethod method = new PostMethod( fCasUrl + SERVICE_VALIDATE_URL_PART );&lt;br /&gt;       method.setParameter( "service", serviceUrl );&lt;br /&gt;       method.setParameter( "ticket", serviceTicket );&lt;br /&gt;       try&lt;br /&gt;       {&lt;br /&gt;           int statusCode = fClient.executeMethod(method);&lt;br /&gt;           if (statusCode != HttpStatus.SC_OK)&lt;br /&gt;           {&lt;br /&gt;               LOG.error( "Could not validate: " + method.getStatusLine() );&lt;br /&gt;               method.releaseConnection();&lt;br /&gt;           } else&lt;br /&gt;           {   &lt;br /&gt;               result = extractUser( new String( method.getResponseBody() ) );&lt;br /&gt;           }&lt;br /&gt;       } catch ( Exception x )&lt;br /&gt;       {&lt;br /&gt;           LOG.error( "Could not validate: " + x.toString () );&lt;br /&gt;           x.printStackTrace();&lt;br /&gt;       }&lt;br /&gt;       method.releaseConnection();&lt;br /&gt;       return result;&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;   /**&lt;br /&gt;    * Authenticate the specified user with the specified password against the&lt;br /&gt;    * specified service.&lt;br /&gt;    *&lt;br /&gt;    * @param serviceUrl May be null. If a url is specified, the authentication will happen against this service, yielding a service ticket which can be validated.&lt;br /&gt;    * @param username&lt;br /&gt;    * @param password&lt;br /&gt;    * @return A valid service ticket, if and only if the specified service URL is not null.&lt;br /&gt;    */&lt;br /&gt;   public String authenticate( String serviceUrl, String username, String password )&lt;br /&gt;   {&lt;br /&gt;       String lt = getLt( serviceUrl );&lt;br /&gt;       if ( lt == null )&lt;br /&gt;       {&lt;br /&gt;           LOG.error( "Cannot retrieve LT from CAS. Aborting authentication for '" + username + "'" );&lt;br /&gt;           return null;&lt;br /&gt;       }&lt;br /&gt;       String result = null;&lt;br /&gt;       PostMethod method = new PostMethod( fCasUrl + LOGIN_URL_PART );&lt;br /&gt;       if ( serviceUrl != null ) // optional&lt;br /&gt;           method.setParameter( "service", serviceUrl );&lt;br /&gt;       method.setParameter( "_eventId", "submit" );&lt;br /&gt;       method.setParameter("username", username );&lt;br /&gt;       method.setParameter("password", password );&lt;br /&gt;       method.setParameter("lt", lt );&lt;br /&gt;       method.setParameter( "gateway", "true" );&lt;br /&gt;       try&lt;br /&gt;       {&lt;br /&gt;           fClient.executeMethod(method);&lt;br /&gt;           if ( serviceUrl == null )&lt;br /&gt;           {&lt;br /&gt;               if ( extractLt( new String( method.getResponseBody() ) ) != null ) // if CAS does not return a login page with an LT authentication was successful&lt;br /&gt;               {&lt;br /&gt;                   LOG.error( "Authentication for '" +  username + "' unsuccessful" );&lt;br /&gt;                   if ( LOG.isDebugEnabled() )&lt;br /&gt;                       LOG.debug( "Authentication for '" + username + "' unsuccessful." );&lt;br /&gt;               } else&lt;br /&gt;               {&lt;br /&gt;                   if ( LOG.isDebugEnabled() )&lt;br /&gt;                       LOG.debug( "Authentication for '" + username + "' unsuccessful." );&lt;br /&gt;               }&lt;br /&gt;           } else&lt;br /&gt;           {&lt;br /&gt;               Header h = method.getResponseHeader( "Location" );&lt;br /&gt;               if ( h != null )&lt;br /&gt;                   result = extractServiceTicket( h.getValue() );&lt;br /&gt;               if ( result == null )&lt;br /&gt;                   LOG.error( "Authentication for '" + username + "' unsuccessful." );&lt;br /&gt;           }&lt;br /&gt;       } catch ( Exception x )&lt;br /&gt;       {&lt;br /&gt;           LOG.error( "Could not authenticate'" + username + "':" + x.toString () );&lt;br /&gt;       }&lt;br /&gt;       method.releaseConnection();&lt;br /&gt;       return result;&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;   /**&lt;br /&gt;    * Helper method to extract the user name from a "service validate" call to CAS.&lt;br /&gt;    *&lt;br /&gt;    * @param data Response data.&lt;br /&gt;    * @return The clear text username, if it could be extracted, null otherwise.&lt;br /&gt;    */&lt;br /&gt;   protected String extractUser( String data )&lt;br /&gt;   {&lt;br /&gt;       String user = null;&lt;br /&gt;       int start = data.indexOf( CAS_USER_BEGIN  );&lt;br /&gt;       if ( start &amp;gt;= 0 )&lt;br /&gt;       {&lt;br /&gt;           start += CAS_USER_BEGIN.length();&lt;br /&gt;           int end = data.indexOf( CAS_USER_END );&lt;br /&gt;           if ( end &amp;gt; start )&lt;br /&gt;               user = data.substring( start, end );&lt;br /&gt;           else&lt;br /&gt;               LOG.warn( "Could not extract username from CAS validation response. Raw data is: '" + data + "'" );&lt;br /&gt;       } else&lt;br /&gt;       {&lt;br /&gt;           LOG.warn( "Could not extract username from CAS validation response. Raw data is: '" + data + "'" );&lt;br /&gt;       }&lt;br /&gt;       return user;&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;   /**&lt;br /&gt;    * Helper method to extract the service ticket from a login call to CAS.&lt;br /&gt;    *&lt;br /&gt;    * @param data Response data.&lt;br /&gt;    * @return The service ticket, if it could be extracted, null otherwise.&lt;br /&gt;    */&lt;br /&gt;   protected String extractServiceTicket( String data )&lt;br /&gt;   {&lt;br /&gt;       String serviceTicket = null;&lt;br /&gt;       int start = data.indexOf( TICKET_BEGIN  );&lt;br /&gt;       if ( start &amp;gt; 0 )&lt;br /&gt;       {&lt;br /&gt;           start += TICKET_BEGIN.length();&lt;br /&gt;           serviceTicket = data.substring( start );&lt;br /&gt;       }&lt;br /&gt;       return serviceTicket;&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;   /**&lt;br /&gt;    * Helper method to extract the LT from a login form from CAS.&lt;br /&gt;    *&lt;br /&gt;    * @param data Response data.&lt;br /&gt;    * @return The LT, if it could be extracted, null otherwise.&lt;br /&gt;    */&lt;br /&gt;   protected String extractLt( String data )&lt;br /&gt;   {&lt;br /&gt;       String token = null;&lt;br /&gt;       int start = data.indexOf( LT_BEGIN  );&lt;br /&gt;       if ( start &amp;lt; 0 )&lt;br /&gt;       {&lt;br /&gt;           LOG.error( "Could not obtain LT token from CAS: LT Token not found in response." );&lt;br /&gt;       } else&lt;br /&gt;       {&lt;br /&gt;           start += LT_BEGIN.length();&lt;br /&gt;           int end = data.indexOf( "\"", start );&lt;br /&gt;           token = data.substring( start, end );&lt;br /&gt;       }       &lt;br /&gt;       return token;&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;   /**&lt;br /&gt;    * This method requests the original login form from CAS.&lt;br /&gt;    * This form contains an LT, an initial token that must be&lt;br /&gt;    * presented to CAS upon sending it an authentication request&lt;br /&gt;    * with credentials.&amp;lt;br&amp;gt;&lt;br /&gt;    * If a service URL is provided (which is optional), this method&lt;br /&gt;    * will post the URL such that CAS authenticates against the&lt;br /&gt;    * specified service when a subsequent authentication request is&lt;br /&gt;    * sent.&lt;br /&gt;    *&lt;br /&gt;    * @param serviceUrl&lt;br /&gt;    * @return The LT token if it could be extracted from the CAS response.&lt;br /&gt;    */&lt;br /&gt;   protected String getLt( String serviceUrl )&lt;br /&gt;   {&lt;br /&gt;       String lt = null;&lt;br /&gt;       HttpMethod method = null;&lt;br /&gt;       if ( serviceUrl == null )&lt;br /&gt;           method = new GetMethod( fCasUrl + LOGIN_URL_PART );&lt;br /&gt;       else&lt;br /&gt;       {&lt;br /&gt;           method = new PostMethod( fCasUrl + LOGIN_URL_PART );&lt;br /&gt;           ( ( PostMethod ) method ).setParameter( "service", serviceUrl );&lt;br /&gt;       }&lt;br /&gt;       try&lt;br /&gt;       {&lt;br /&gt;           int statusCode = fClient.executeMethod(method);&lt;br /&gt;           if (statusCode != HttpStatus.SC_OK)&lt;br /&gt;           {&lt;br /&gt;               LOG.error( "Could not obtain LT token from CAS: " + method.getStatusLine() );&lt;br /&gt;               method.releaseConnection();&lt;br /&gt;           } else&lt;br /&gt;           {&lt;br /&gt;               Object o = method.getResponseHeaders() ;&lt;br /&gt;               return extractLt( new String( method.getResponseBody() ) );&lt;br /&gt;           }&lt;br /&gt;       } catch ( Exception x )&lt;br /&gt;       {&lt;br /&gt;           LOG.error( "Could not obtain LT token from CAS: " + x.toString () );&lt;br /&gt;       }&lt;br /&gt;       method.releaseConnection();&lt;br /&gt;       return lt;&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8172024450972453037-2987552901478650279?l=mathiasrichter.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://mathiasrichter.blogspot.com/feeds/2987552901478650279/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8172024450972453037&amp;postID=2987552901478650279" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/2987552901478650279?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/2987552901478650279?v=2" /><link rel="alternate" type="text/html" href="http://mathiasrichter.blogspot.com/2008/04/standalone-java-cas-client.html" title="Standalone Java CAS Client" /><author><name>Mathias</name><uri>http://www.blogger.com/profile/13180258123551210286</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://3.bp.blogspot.com/_fNAEyWEDYJQ/Srfk1UYYVfI/AAAAAAAAAAM/qtiafPzZGZU/S220/Mathias.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;DkAHQ3w-cCp7ImA9WxZUFUQ.&quot;"><id>tag:blogger.com,1999:blog-8172024450972453037.post-7361357572153290543</id><published>2008-04-07T20:43:00.003+02:00</published><updated>2008-04-07T20:58:52.258+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-07T20:58:52.258+02:00</app:edited><title>Making Spring Dynamic Modules work</title><content type="html">&lt;span style="font-size:85%;"&gt;Spring Dynamic Modules (Spring support for OSGi) is a great thing. Just in case I am not the only one who struggled to get the required bundles straight, here goes. These are the bundles that I had to import to make Spring DM work on the Equinox OSGi implementation:&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;pre   style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); line-height: 14px; width: 100%;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;font-size:12px;"&gt;&lt;span style="font-size:85%;"&gt;&lt;code&gt;aopalliance.osgi-1.0-SNAPSHOT.jar&lt;br /&gt;backport-util-concurrent.osgi-3.1-SNAPSHOT.jar&lt;br /&gt;jcl104-over-slf4j-1.4.3.jar&lt;br /&gt;log4j.osgi-1.2.15-SNAPSHOT.jar&lt;br /&gt;slf4j-api-1.4.3.jar&lt;br /&gt;slf4j-log4j12-1.4.3.jar&lt;br /&gt;spring-aop-2.5.1.jar&lt;br /&gt;spring-beans-2.5.1.jar&lt;br /&gt;spring-context-2.5.1.jar&lt;br /&gt;spring-core-2.5.1.jar&lt;br /&gt;spring-osgi-core-1.0.jar&lt;br /&gt;spring-osgi-extender-1.0.jar&lt;br /&gt;spring-osgi-io-1.0.jar&lt;br /&gt;&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-size:85%;"&gt;&lt;br /&gt;Install these and then make sure that you install a bundle which contains the LOG4J configuration of your choice. This is described &lt;a href="http://www.eclipsezone.com/eclipse/forums/t99588.rhtml"&gt;here&lt;/a&gt;, so I won't go over that again.&lt;br /&gt;&lt;br /&gt;BTW, the version numbers on the above bundles might change, if you use later versions of Spring DM and their dependencies.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8172024450972453037-7361357572153290543?l=mathiasrichter.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://mathiasrichter.blogspot.com/feeds/7361357572153290543/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8172024450972453037&amp;postID=7361357572153290543" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/7361357572153290543?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/7361357572153290543?v=2" /><link rel="alternate" type="text/html" href="http://mathiasrichter.blogspot.com/2008/04/making-spring-dynamic-modules-work.html" title="Making Spring Dynamic Modules work" /><author><name>Mathias</name><uri>http://www.blogger.com/profile/13180258123551210286</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://3.bp.blogspot.com/_fNAEyWEDYJQ/Srfk1UYYVfI/AAAAAAAAAAM/qtiafPzZGZU/S220/Mathias.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DkAMQHkzeSp7ImA9WxZUFUQ.&quot;"><id>tag:blogger.com,1999:blog-8172024450972453037.post-5509167543095883203</id><published>2008-04-06T11:34:00.008+02:00</published><updated>2008-04-07T20:59:41.781+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-07T20:59:41.781+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="configuration" /><category scheme="http://www.blogger.com/atom/ns#" term="plugin" /><category scheme="http://www.blogger.com/atom/ns#" term="osgi" /><category scheme="http://www.blogger.com/atom/ns#" term="extensibility" /><title>How to launch an OSGi runtime in your application with Equinox</title><content type="html">&lt;span style="font-size:85%;"&gt;Here's a useful code snippet for launching an OSGi runtime from within your application. This is useful where you want to use OSGi in your own application as a plug-in mechanism.&lt;br /&gt;&lt;br /&gt;I use Equinox, the Eclipse OSGi implementation.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 10px; line-height: 14px; width: 100%;"&gt;&lt;code&gt;import org.eclipse.osgi.baseadaptor.BaseAdaptor;&lt;br /&gt;import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;&lt;br /&gt;import org.eclipse.osgi.framework.internal.core.OSGi;&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Launch the OSGi framework.&lt;br /&gt;*&lt;br /&gt;* @param installArea Directory path to where the OSGi bundles remain.&lt;br /&gt;* @param configArea Direcotory path to where OSGi should store its configuration                data.&lt;br /&gt;*/&lt;br /&gt;public void launchOsgi( String installArea, String configArea )&lt;br /&gt;{&lt;br /&gt;    System.setProperty( "osgi.install.area", installArea );&lt;br /&gt;    System.setProperty( "osgi.configuration.area", configArea );&lt;br /&gt;    FrameworkAdaptor adaptor = new BaseAdaptor( null );&lt;br /&gt;    OSGi osgiInstance = new OSGi( adaptor );&lt;br /&gt;    osgiInstance.launch();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-size:85%;"&gt;&lt;br /&gt;&lt;br /&gt;The installation area points to a location in the file system where you want to make your OSGi runtime believe it is "installed", i.e. where it would look for bundles if you used the &lt;code&gt;install file:mybundle_1.0.0.jar&lt;/code&gt; command.&lt;br /&gt;&lt;br /&gt;The config area points to the location where you want your OSGi runtime to store configuration information about bundles you install while it is running. As you probably know, OSGi stores information about every bundle you install such that upon the next restart, you will not have to install all bundles from scratch again.&lt;br /&gt;&lt;br /&gt;Instead of setting system properties, which is admittedly a bit cumbersome, you could pass parameters to the &lt;code&gt;BaseAdaptor&lt;/code&gt; constructor, but I havent figured out the parameter names yet :-)&lt;br /&gt;&lt;br /&gt;From the OSGi instance you have created you can use &lt;code&gt;osgiInstance.getBundleContext()&lt;/code&gt; to install and manage bundles.&lt;br /&gt;&lt;br /&gt;That's basically all you need to build an OSGi-based plugin mechanism into your application.&lt;br /&gt;&lt;br /&gt;Well, almost. Of course you will need to write some code to detect new bundles in the install area or other locations (at start time of your application or dynamically at runtime of your application). You could use functionality like Peter Kriens' &lt;a href="http://www.aqute.biz/Code/FileInstall"&gt;FileInstall &lt;/a&gt;bundle, for example. Equinox has something similar but I have not looked at it yet :-)&lt;br /&gt;&lt;br /&gt;I might cover this topic in one of my future posts.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8172024450972453037-5509167543095883203?l=mathiasrichter.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://mathiasrichter.blogspot.com/feeds/5509167543095883203/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=8172024450972453037&amp;postID=5509167543095883203" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/5509167543095883203?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8172024450972453037/posts/default/5509167543095883203?v=2" /><link rel="alternate" type="text/html" href="http://mathiasrichter.blogspot.com/2008/04/how-to-launch-osgi-runtime-in-your.html" title="How to launch an OSGi runtime in your application with Equinox" /><author><name>Mathias</name><uri>http://www.blogger.com/profile/13180258123551210286</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://3.bp.blogspot.com/_fNAEyWEDYJQ/Srfk1UYYVfI/AAAAAAAAAAM/qtiafPzZGZU/S220/Mathias.jpg" /></author><thr:total>1</thr:total></entry></feed>

