February 2nd, 2011
Occasionally I make an attempt to learn Ruby. The language has a nicely orthogonal OOP implementation but it’s not one of the C-like languages I know so the learning curve is steep. I thought I’d try using Ruby to access our new CMS.
Our web content management system (CMS) uses a data storage system called Content Repository Extreme (CRX) by Day (now Adobe). It is a hierarchical data structure based on the Java Content Repository (JCR) API. A reference implementation is Apache Jackrabbit.
Content is typically created via a web based interface. Custom functionality is provided via JSP/scriptlets. However the repository can also be accessed by standalone applications. Since access is defined by a Java API, one must program in Java or a JVM-based language. JRuby falls into the latter camp.
This article describes how to access Jackrabbit using JRuby. It describes such setup work as obtaining the jackrabbit jar file and copying it to the JRuby libs directory. It also explains line 2 where the Java String class is renamed to JString in Ruby to avoid a name collision. While this article describes how to access CRX with Java. The http access method is slower than the RMI interface but I couldn’t get the latter to work with JRuby. Probably because I am missing some jar file. Translating the Java into Ruby gives the following program which lists all the nodes in the repository by their path name.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| require 'java'
include_class('java.lang.String') {|package,name| "J#{name}" }
include_class 'javax.jcr.Repository'
include_class 'javax.jcr.SimpleCredentials'
include_class 'org.apache.jackrabbit.rmi.client.ClientRepositoryFactory'
include_class 'org.apache.jackrabbit.commons.JcrUtils'
def listChildren (indent, node)
puts indent+ node.getPath
node.getNodes.each do |n| listChildren indent+" ",n end
end
# jackrabbit
#repository = JcrUtils.getRepository("http://localhost:8080/rmi")
#creds = SimpleCredentials.new("user", JString.new("pass").toCharArray)
# crx
repository = JcrUtils.getRepository("http://localhost:4502/crx/server")
#repository = JcrUtils.getRepository("rmi://localhost:1099/crx")
creds = SimpleCredentials.new("admin", JString.new("admin").toCharArray)
session = repository.login(creds)
name = repository.getDescriptor(Repository::REP_NAME_DESC);
user = session.getUserID
puts "logged in as " + user + " in " + name
root = session.getRootNode
puts "root property = " + root.getProperty("jcr:primaryType").getString
listChildren("", root)
session.logout |
The first few lines of output look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| logged in as administrator in CRX
root property = rep:root
/
/rep:policy
/rep:policy/allow
/rep:policy/allow0
/jcr:system
/jcr:system/jcr:versionStorage
/jcr:system/jcr:versionStorage/1b
/jcr:system/jcr:versionStorage/1b/70
/jcr:system/jcr:versionStorage/1b/70/dd
/jcr:system/jcr:versionStorage/1b/70/dd/1b70dd8a-f933-4d62-b6b4-b5ab1aaef8f9
/jcr:system/jcr:versionStorage/1b/70/dd/1b70dd8a-f933-4d62-b6b4-b5ab1aaef8f9/jcr:versionLabels
/jcr:system/jcr:versionStorage/1b/70/dd/1b70dd8a-f933-4d62-b6b4-b5ab1aaef8f9/jcr:rootVersion |
Here’s the Java version:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| package jackrabbit;
import javax.jcr.*;
import java.net.MalformedURLException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import javax.naming.NamingException;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.jackrabbit.rmi.client.ClientRepositoryFactory;
import org.apache.jackrabbit.jcr2spi.config.RepositoryConfig;
import org.apache.jackrabbit.webdav.jcr.nodetype.NodeTypeConstants;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.logging.LogFactory;
public class Main {
public static void main(String[] args) throws ClassCastException, MalformedURLException, RemoteException, NotBoundException, LoginException, NoSuchWorkspaceException, RepositoryException, NamingException {
//RMI Connection
Repository repository =
// WebDav remoting access to CRX server (port 7402?)
//JcrUtils.getRepository("http://localhost:4502/crx/server");
// RMI remoting access to a CRX server (with RMI enabled)
//JcrUtils.getRepository("http://localhost:8080/rmi");
// RMI remoting access to a CRX server (with RMI enabled)
JcrUtils.getRepository("rmi://localhost:1099/crx");
//Workspace Login
SimpleCredentials creds =
// crx
new SimpleCredentials("admin", "admin".toCharArray());
// jackrabbit takes anything
//new SimpleCredentials("user", "pass".toCharArray());
Session session = null;
session = repository.login(creds);
String user = session.getUserID();
//List Children
System.out.println("User: "+user+ ", Workspace: " + session.getWorkspace().getName() + "\n");
listChildren("", session.getRootNode());
}
private static void listChildren(String indent, Node node) throws RepositoryException {
System.out.println(indent + node.getPath());
NodeIterator ni = node.getNodes();
while (ni.hasNext()) {
listChildren(indent + " ", ni.nextNode());
}
}
} |
Compared with Java the Ruby is somewhat more concise. The iterator syntax is much cleaner. There’s not need to write a while-loop with method calls to hasNext or nextNode. Running a scripting language under a JVM facilitates access to Java-based legacy data. I could see using such a standalone application to produce custom reports or making a global modification.
Tags: content management system, ruby
Posted in work | Comments Off
January 18th, 2011
Over the weekend we had a php application that was scheduled to close at midnight. Instead about 7pm the complaints started to roll in that the application had closed early. The application used the php function strtotime to determine when to close. If past the deadline then the application redirected to a message page.
1
2
3
4
| if (strtotime("2011-01-15 11:59PM") < strtotime("now")){
$this->redirect(array('controller' => 'applications', 'action' => 'deadline'));
return;
} |
With the UNIX command line “date” I checked the date and time zone on the server. It was correct. I ran the following PHP script on the server to check PHP’s configuration (both under command line and under apache). It too reported the correct time and the zone as American/New York.
1
2
3
4
5
6
7
8
9
10
11
12
13
| <?php
$a = strtotime("2011-01-15 11:59PM")."\n";
$t = strtotime("now")."\n";
echo " fix:$a\n";
echo " now:$t\n";
echo "date fix:".date("c",$a)."\n";
echo "date now:".date("c",$t)."\n";
if (strtotime("2011-01-15 11:59PM") < strtotime("now"))
echo "1/15 11:59PM less than now\n";
if (strtotime("2011-01-15 11:59PM") > strtotime("now"))
echo "1/15 11:59PM GREATER than now\n";
echo "zone:".date_default_timezone_get()."\n"; |
However the application was running under the CakePHP framework. So I wrote a dummy controller function and put the above code in a dummy view. In this context the script reported the time zone as “UTC” and the time as five hours earlier! Turns out CakePHP explicitly sets the time zone to UTC. So it doesn’t matter what the time zone setting is on the server or in the php.ini configuration file. I certainly did not expect that.
Did I test the code? Yes but I ran it at 4pm. The difference between UTC and EST is five hours so they were both on the same date at the time of the test.
This was the second time CakePHP behaved unexpectedly in as many months. A couple months ago we switched databases from MySQL to SQL Server. At first it appeared that CakePHP seamlessly dealt with the transition. Then we got a complaint from a client that an order was missing.
The application had a option to upload a Word document as a file and this file was stored in the database. If the file is larger than a certain size then this operation silently fails. CakePHP does not report an error. In the php.ini file the config parameters default to only 4096. Large enough to pass a simple test but too small for many files. Fortunately someone had posted about this in the comments for the mssql_connect function.
1
2
3
4
5
| ; Valid range 0 - 2147483647. Default = 4096.
mssql.textlimit = 4096
; Valid range 0 - 2147483647. Default = 4096.
mssql.textsize = 4096 |
Twice in two months CakePHP has provided unexpected results. Once by resetting the time zone and in another for silently failing a database operation. Convention over configuration can save development time but production problems are even more costly. I’m left wondering how many other unexpected items are lurking.
Posted in work | Comments Off
December 15th, 2010
Unlike the CakePHP framework, CodeIgniter has no direct support for common headers/footers. There are a few suggests in their wiki but they have disadvantages. Compared to extending the Controller class or a separate template library; nested/partial views has the advantage of simplicity by using built-in facilities.
Here’s a small example I use for illustration.
In the Flowershop one is presented with a form and selects a color. The application responds with a list of flowers of that color. A single, common view is used for every page. This common view is handed a page title, navigation view and content view.
Here’s the controller method. The view loaded is “common/default”. In the array passed to the view are the title, a page navigation view and a page content view.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function flowersByColor() {
$this->load->model('colorsModel');
$this->load->model('flowersModel');
$colors = $this->colorsModel->listAllColors();
$flowers = null;
if (isset($_POST['color_id'])) {
$flowers = $this->flowersModel->getFlowersByColorId($_POST['color_id']);
}
$this->load->view("common/default", // common view for every page
array(
'title'=>'Flowers by Color', // page title
'navigation'=>'flowers/nav', // page navigation view
'content'=>"flowers/flowersbycolor", // page content view
'colors'=>$colors, // list of colors
'flowers'=>$flowers)); // list of flowers
} |
Here’s the view common/default.php. It sets the page title, loads a page navigation view and the page content view.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?php echo doctype('html4-trans')."\n"; ?>
<html>
<head>
<title><?php echo $title; ?></title>
<?php echo link_tag('css/style.css')."\n";?>
</head>
<body>
<?php $this->load->view("common/banner"); ?>
<?php $this->load->view($navigation); // page navigation?>
<div id="content">
<?php $this->load->view($content); // page content ?>
</div>
</body>
</html> |
Here is view flowers/flowersbycolor.php. It is the page content view. It displays a form to input a color and if a list of flowers is present, it loads the view flowers/tableView.php that displays the list. Note that when this view is loaded, one need not re-specify the $flowers variable for the page content.
1
2
3
4
5
6
7
8
9
10
11
12
13
| <h2>Flowers by color</h2>
<?php
echo form_open('flowers/flowersbycolor');
//convert array of objects to associative array
foreach ($colors as $color) $a[$color->color_id] = $color->color;
echo form_label('Color: ', 'color_id').form_dropdown('color_id', $a)."<br/>\n";
echo form_submit("submit", "Submit");
echo form_close();
if ($flowers) {
$this->load->view("flowers/tableView");
}
?> |
Here is the view flowers/tableView.php. It displays a table of flowers. The view is shared by other controllers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| <table border="1">
<tr>
<th>Edit</th>
<th>Common Name</th>
<th>Color</th>
<th>Season</th>
<th>Description</th>
</tr>
<?php foreach ($flowers as $flower?>
<tr>
<td><a href="edit/<?php echo $flower->flower_id; ?>">edit</a>
<a href="view/<?php echo $flower->flower_id; ?>">view</a>
<a href="delete/<?php echo $flower->flower_id; ?>">delete</a>
</td>
<td><?php echo $flower->commonname; ?></td>
<td><?php echo $flower->color; ?></td>
<td><?php echo $flower->season; ?></td>
<td><?php echo $flower->description; ?></td>
</tr>
<?php endforeach; ?>
</table> |
Nested, partial views can be used to create a template for pages with common headers and footers without resorting to extending controllers or separate libraries.
Tags: codeigniter, PHP
Posted in work | Comments Off
December 14th, 2010
Recently our group reviewed several PHP MVC frameworks. MVC is model, view, controller; a software system architecture that separates data from user presentation from business logic. Four of the most popular are CakePHP, CodeIgniter, Symfony, and Zend Framework. Google trends has a nice graph. We eliminated symfony and Zend Frameworks due to complexity and steep learning curve.
Cake and CI have many similarities:
- MVC architecture
- Easy installation
- Open source license
- REST-like URLs
- Good Documentation
- Supportive user community
- Page caching
- Maturity
While they have many similarities, Cake has a number of advanced features: Obejct-Relational Mapper, Scaffolding via the Bake utility, page layout and convention over configuration. These conventions and features produce a steep learning curve. Initially one will be more productive with CI. It’s documentation and tutorials are outstanding. One way to illustrate this learning curve is by comparing the controllers for a simple input form.
In the Flowershop application, one inputs a color and the application lists corresponding flowers. Here’s the CodeIgniter controller method. Notice the first couple of lines which load the models for the colors and flowers. Also notice the last line which loads the view. In between are method calls to get a list of all the colors (getAllColors()) and get a list of flowers by color (getFlowersByColorID()). The get methods are defined in the corresponding models. There’s also a reference to $_POST['color_id']. $_POST is the global array used by PHP for form input using the post method.
1
2
3
4
5
6
7
8
9
10
| function flowersByColor() {
$this->load->model('colorsModel');
$this->load->model('flowersModel');
$colors = $this->colorsModel->getAllColors();
$rows = null;
if (isset($_POST['color_id'])) {
$rows = $this->flowersModel->getFlowersByColorId($_POST['color_id']);
}
$this->load->view("flowers/flowersbycolor", array('colors'=>$colors,'rows'=>$rows));
} |
Here’s the same controller method in CakePHP. Thanks to Cake’s conventions, its somewhat shorter. Notice that the models are not explicitly loaded. Since the Flower model shares the same name as the controller class, it is loaded implicitly. Since a relationship is specified between the Flower and Color models, the Color model is also implicitly loaded. Notice that a view is not explicitly loaded. A view is implicitly loaded which shares the controller method’s name.
Notice the model methods “find” and “findAllByColorID”. In CI they had to be written by the developer. In Cake they are generated automatically.
Notice the absence of the typical PHP $_POST array. The input form data are accessed through $this->data. The use of this array is required to pass information to the automatically generated model methods.
1
2
3
4
5
6
7
| function flowersbycolor() {
$colors = $this->Flower->Color->find('list',array('fields'=>array('Color.color_id','Color.color')));
$this->set(compact('colors'));
if (!empty($this->data)){
$a = $this->Flower->findAllByColorId($this->data['Flower']['color_id']);
$this->set('flowers',$a);
} |
Compared to CodeIgniter, CakePHP has a number of advanced features. The more I use Cake the more I prefer it. Clearly it would be better in the long run. The problem is that my time is spread between PHP, Java/JSP and occasionally classic ASP. As a result I don’t have time to become proficient in any one tool. We also employ student workers and due to their turnover, they don’t have the time either. Consequently Cake’s steeper learning curve has become an obstacle to its adoption. CI’s transparent architecture, coupled with it’s superior documentation means I can easily pick up an old application and understand it.
Tags: cakephp, codeigniter, PHP
Posted in work | Comments Off
November 28th, 2010
Of late I’ve become enamored with PHP’s json_encode function. This flexible function takes any PHP item like an object or array and encodes it as a json string. This string is then sent to a client JavaScript application. Since the string is JavaScript, native syntax like dot and brackets are used to access the objects and arrays. If the data were in XML then clunky method calls would be required.
I converted my cincybackpackers.com web site to use json_encode. This site is build with CodeIgniter an MVC based framework for PHP. The json strings were custom built in PHP using views. By eliminating the views and putting the code into controllers, I’m able to reduce server side code (I wonder what the performance is like). The new controller code follows. It creates an array of objects via a database query. Then it sets the http header content-type to application/x-javascript and echoes the json_encode of the query result to the client.
1
2
3
4
5
6
| function json($place_id) {
$this->load->model("TripsModel");
$result = $this->TripsModel->list_trips_by_place($place_id);
header('Content-type: application/x-javascript');
echo json_encode($result);
} |
These changes were mostly trivial but there was a dependency. The database column names must be the same field names used in the client JavaScript. For the most part I followed this rule but there was an important exception. The JavaScript code referred to the primary key of a table generically as “id”. The actual name of the column is usually “trip_id” or “place_id”. This required some re-write of SQL. Instead of a simple “SELECT trip_id”, I used “SELECT trip_id as id”.
There was another bit of code where an xml string is converted to json. In this case I used PHP’s function simplexml_load_string to convert the xml to a PHP object. Then function json_encode was used to create the json.
1
2
3
4
5
6
7
| function jsongpx($trip_id) {
$this->load->model('TripsModel');
$result = $this->TripsModel->list_trips_gpx_by_trip_id($trip_id);
$gpx = simplexml_load_string($result[0]->gpx);
header('Content-type: application/x-javascript');
echo json_encode($gpx);
} |
While json_encode greatly simplified the server side code, the resulting json bore little resemblance to my custom json. The main difference is in the treatment of singletons and arrays. For example the custom json always created an array for way points. Using simplexml_load_string, if the file did not contain a way point then nothing is output. If the file contained a single way point, then a singleton object was output. If the file contained multiple way points then an array was output. As a result the client side JavaScript is more complex as follows:
1
2
3
4
5
6
7
8
9
10
11
12
| // waypoints
if (gpx.wpt){ // any way points at all?
if (gpx.wpt.length) { // array of way points
for (i = 0; i < gpx.wpt.length-1; i++)
{
gm[i] = createMarker(gpx.wpt[i]);
}
}
else { // single way point
gm[0] = createMarker(gpx.wpt);
}
} |
The PHP function json_encode can be used to reduce server side code but one must be careful of name dependencies between the database and JavaScript code. Also the resulting output may differ significantly from custom json output. and require JavaScript revision.
Tags: JavaScript, json, json_encode, PHP
Posted in work | Comments Off