Learning Clojure: Setting up the Emacs on OSX

I’ve been using the Emacs since my Lisp and AI course back in 1987. (My use of the definite article betrays that :) ) So, of course I wanted to learn Clojure using it. Unfortunately, there are either incomplete or conflicting posts in googlespace on how to go about doing this. You *can* grab the source with git and build etc., but let’s try the simplest thing possible.

xkd comic

There are two ways to have the Emacs interact with Clojure: inferior-lisp-mode and Slime. I will cover both here.

Initial setup

Clojure runs on the JVM (there are separate projects for the CLR and also ClojureScript), so you should have a JDK version 1.5 or later installed. If you have not set up Leiningen, or even know what that is, please read the first article in this series on setting that up.

Emacs

I was using Emacs 23. It is a bit more difficult setting it up and managing 23 since integration with the Emacs Lisp Package Archive isn’t baked in. So, just get Emacs 24.

For OSX, get the latest Emacs 24 pretest build. I – and many others – have been using it with no problems in spite of it not being a “release.”

Of course I had my Emacs init files. You may have either a ~/.emacs or ~/.emacs.d/init.el lying around. I renamed them (e.g. .emacs.d.bak) to start fresh and then launched Emacs. Just a reminder, if you have a ~/.emacs file, ~/.emacs.d/init.el will be ignored.

I edited my new ~/.emacs.d/init.el to include an additional elisp repository which hosts clojure-mode and paredit.

1
2
3
4
(require 'package)
(add-to-list 'package-archives
             '("marmalade" . "http://marmalade-repo.org/packages/") t)
(package-initialize)

Go ahead and evaluate the buffer (M-x eval-buffer).

The sequence of Emacs commands follows. You first refresh the local list of available packages and writes that list into a directory in your ~/.emacs.d. This will take bit of time. Then you install the clojure-mode package.

1
2
M-x package-refresh-contents
M-x package-install RET clojure-mode RET

If you want, you can run M-X package-list-packages to see what’s there, but I suggest that you don’t blindly add packages without first knowing if they play nicely together.

Inferior Lisp mode

Edit your ~/.emacs.d/init.el again and add these lines. If you’re not on OSX, ignore the third line which changes the Meta key from the Option key to the Command key.

1
2
3
4
5
6
7
(setq inferior-lisp-program "lein repl")
 
;; where is lein located? If it's not in a "standard path, add a line like this.
(add-to-list 'exec-path "/Users/gene/bin")
 
;; on OSX make the command key the meta key
(setq ns-command-modifier 'meta)

Now we can launch a REPL in a subprocess within the Emacs. This is Inferior Lisp Mode.
Open up a test clojure file (e.g. C-x f foo.clj), or navigate to a lein project you created in the first article in this series and edit or create one there.
Enter M-x run-lisp (or C-c C-z).
This will tell lein to start a REPL, since that is what you set the variable inferior-lisp-program to.
Now split the window by typing C-x 2. In one of the windows, navigate to your Clojure file.
Enter a simple sexp. Then after the final ), type C-x C-e to evaluate it. (clojure-mode binds this key sequence to lisp-eval-last-sexp, as well as C-c C-e so you don’t have to remember which!. As always, you can type M-x m for mode info.) You will see the result in the *inferior-lisp* REPL buffer.

(+ 2 2)

You can also go into the REPL buffer and type expressions there and they will be evaluated. Editing is fairly basic, but it may be all that you need. Indeed, if you’ve seen the screencasts by Rich Hickey,the creator of Clojure, you will see that he is using inferior Lisp mode.

Clojure mode key bindings.

C-c C-e evaluate expression to the left of the cursor
C-x C-e same thing
C-c C-r evaluate expression in region
C-c C-z run lisp
C-M-x evaluate defn
C-M-q indent
There are others. Type M-x m for more.

Slime

The Superior Lisp Interaction Mode for Emacs (Slime) has many more features, including a debugger, and code completion. It was written for other Lisps and is based on a client server model. Slime is the Emacs client and Swank is the “server”. That means you can run Clojure on a different machine if you’d like. For this article, we’ll stick with using it locally.

Swank

Since you already are using Leiningen, simply add the swank plugin. It will be downloaded to ~/.lein/plugins.

$ lein plugin install lein-swank 1.4.4
Including lein-swank-1.4.4.jar
Created lein-swank-1.4.4.jar
$

You can check current versions like this:

$ lein search lein-swank

Navigate to your project: using dired is easiest. Then do this:

M-x clojure-jack-in

This starts a Swank server, then uses Slime to connect to it from Emacs.
If you get an error stating that lein cannot be found, see OSX quirks.
Slime’s keybindings are a bit different from Inferior Lisp mode. I suggest that you type M-x m and scroll down to the Slime section or read the Swank Clojure Readme.

Since your are still learning Clojure, Slime’s inspector is quite handy. Place the cursor after a symbol or expression and type C-c I (I as in Inspector). The minibuffer will ask for confirmation and then display the result.
The other handy exploration function is slime-edit-definition. Place the cursor after a function, e.g. after the + in (+ 2 2), then type M-. (meta period). The source code for the definition of + will appear in a buffer.
Visit Slime’s home page for more information. There is a screencast there now showing it in action with Common Lisp.

Alternatives

There are two alternative ways of specifying that you want to use Swank: through a project independent profile and inside a project.

For the global profile, add this to ~/.lein/profiles.clj

{:user {:plugins [ [lein-swank "1.4.4"] ]}}

Or add this to your project.clj if you want it inside your project. I prefer to not do this since other team members might be using another editor.

1
2
3
(defproject lein01 "1.0.0-SNAPSHOT"
  :description "test project"
  :plugins [[lein-swank "1.4.4"]]

OSX quirk

Here are a few of my OSX specific entries from .emacs.d/init.el. I prefer to use the command key as Emacs’ meta key. If you placed the lein script outside of “normal” paths, (e.g. ~/bin) you may have a problem with lein not being found inside Emacs. If that is the case, you can add your PATH that you set in .bash_profile to Emacs.

1
2
3
4
5
6
7
;; on OSX make the command key the meta key
(setq ns-command-modifier 'meta)
 
;; Read in PATH from .bash_profile
(if (not (getenv "TERM_PROGRAM"))
     (setenv "PATH"
            (shell-command-to-string "source $HOME/.bash_profile && printf $PATH")))

Emacs Starter Kit

I suggest that you also install Phil Hagelberg’s Emacs Starter Kit. It includes a good set of defaults, as well as ido-mode and paredit.

M-x package-install RET starter-kit RET

If you don’t want the starter kit, at least install paredit.

M-x package-install RET paredit RET

Then add this to your ~/.emacs.d/init.el

(autoload 'paredit-mode "paredit"
      "Minor mode for pseudo-structurally editing Lisp code." t)
(add-hook 'emacs-lisp-mode-hook    (lambda () (paredit-mode +1)))
(add-hook 'clojure-mode-hook       (lambda () (paredit-mode +1)))

Of course the starter kit does this for you. Read the documentation at the Emacs Wiki on how to use paredit mode.

Have fun.

Resources

Books

Posted in Clojure, IDE | Tagged , , , , | Leave a comment

Learning Clojure: Leiningen

Where to start with your Clojure adventure? You’re lucky because there are published books that are available now. Unfortunately for the beginning learner, they gloss over the very first real steps; once you get a REPL running, they’re done. This is step 1.1.

My assumption is that you are new to Clojure, but you know Java and perhaps Maven. Since Clojure runs on the JVM, you need to a Java development kit 1.5 or later already installed.

In this article you will configure Clojure’s build tool Leiningen and use it to start a REPL. Inside the REPL – the Read Evaluate Print Loop – you execute your Clojure code. That is all we’re doing here: installing Leiningen and getting it to execute a REPL. In another article, I show you how to use
the Emacs with Leiningen.

Getting Clojure

Yes, you can download it and you can run a REPL with the jar.

$ java -cp clojure-1.3.0.jar clojure.main
REPL started; server listening on localhost port 57274
user=> (+ 2 2)
4
$

Or, if you just want a taste, you can just try Clojure online.

But honestly, in real life you are going to be using some sort of build tool. So, why not just start there? Coming from Java you may have used ANT or Maven. You can use Maven with Clojure, but if you’ve spent as much time as me whacking away at pom.xml files, you may be open to something different. Even if you just want to hack away in a REPL, I suggest you install Leiningen.

Leiningen

Leniningen is “Maven for Clojure.” It even uses Maven repositories. It is also an easy way to just get started hacking at Clojure. The instructions to install Leiningen are straightforward – download a shell script, put it in your path, and execute it. Then run the self-install target.

$ cd /to/a/directory/that/is/in/your/PATH
$ curl -O https://raw.github.com/technomancy/leiningen/stable/bin/lein
$ chmod 755 lein
$ lein self-install

This will create a directory named ~/.lein that will contain configuration and lein plugins. Take a look if you want, but you don’t have to.

The first thing I suggest that you do with Leiningen is to create a new project.

$ lein new lein01
Created new project in: /Users/gene/clojureprojects/lein01
$ cd lein01
$ lein classpath
/Users/gene/clojureprojects/lein01/test:/Users/gene/clojureprojects/lein01/test-resources:/Users/gene/clojureprojects/lein01/src:/Users/gene/clojureprojects/lein01/classes:/Users/gene/clojureprojects/lein01/resources
$

Go ahead and examine the directory/file structure. It is similar to a Maven project. project.clj is your pom.xml, but it is in Clojure instead of XML.

1
2
3
(defproject lein01 "1.0.0-SNAPSHOT"
  :description "FIXME: write description"
  :dependencies [[org.clojure/clojure "1.2.1"]])

Running lein deps will create a lib directory in your project and copy the dependencies specified in your project.clj into it. The contents of the lib directory will be appended to your classpath.

$ ls lib
ls: lib: No such file or directory
$ ls
README		classes		project.clj	src		test
$ lein deps
Copying 1 file to /Users/gene/clojureprojects/lein01/lib
$ ls lib
clojure-1.2.1.jar
$ lein classpath
/Users/gene/clojureprojects/lein01/test:/Users/gene/clojureprojects/lein01/test-resources:/Users/gene/clojureprojects/lein01/src:/Users/gene/clojureprojects/lein01/classes:/Users/gene/clojureprojects/lein01/resources:/Users/gene/clojureprojects/lein01/lib/clojure-1.2.1.jar

Now, assuming you are in the project directory, simply type the following:

$ lein repl
REPL started; server listening on localhost port 33198
user=> (+ 2 2)
4
user=> (doc +)
-------------------------
clojure.core/+
([] [x] [x y] [x y & more])
  Returns the sum of nums. (+) returns 0.
nil
user=>

You now have a REPL with your project’s classpath all set up. Since you haven’t touched project.clj yet, that doesn’t really matter right now, but it will when you start to add dependencies.

You really don’t have do any further setup to start playing with Clojure. Grab a Clojure book/tutorial and whack at the REPL. Control-D to quit. Eventually though, you will probably want an editor.

Displaying the classpath inside the REPL

We’ve seen the classpath from the command line using lein classpath. But once we are inside a REPL, how do you discover the classpath? There are several libraries under the rubric “contrib” that are outside Clojure proper. Until recently, contrib was one huge monolithic jar. It is now separate projects. One of these contrib projects is named java.classpath. Some details are on the project’s home page. and you can read the documentation.

The first thing we need to do is to update the project.clj file and add a new dependency like this:

(defproject lein01 "1.0.0-SNAPSHOT"
  :description "FIXME: write description"
  :dependencies [[org.clojure/clojure "1.2.1"]
 		[org.clojure/java.classpath "0.2.0"]
		])

Then update your dependencies with lein. Several repositories are tried until the jar is found.

$ lein deps
Downloading: org/clojure/java.classpath/0.2.0/java.classpath-0.2.0.pom from central
Downloading: org/clojure/pom.contrib/0.0.21/pom.contrib-0.0.21.pom from central
Downloading: org/clojure/clojure/1.3.0-alpha6/clojure-1.3.0-alpha6.pom from clojars
Downloading: org/clojure/clojure/1.3.0-alpha6/clojure-1.3.0-alpha6.pom from jboss
Downloading: org/clojure/clojure/1.3.0-alpha6/clojure-1.3.0-alpha6.pom from jboss-public-repository-group
Transferring 5K from jboss-public-repository-group
Downloading: org/clojure/java.classpath/0.2.0/java.classpath-0.2.0.jar from central
Copying 2 files to /Users/gene/clojureprojects/lein01/lib
$

Now you can start your REPL and use a function, cunningly named “classpath” from this contrib library.

$ lein repl
REPL started; server listening on localhost port 12414
user=> (use 'clojure.java.classpath)
nil
user=> (clojure.java.classpath/classpath)
(#<File /Users/gene/clojureprojects/lein01/test> 
#<File /Users/gene/clojureprojects/lein01/src> 
#<File /Users/gene/clojureprojects/lein01/classes> 
#<File /Users/gene/clojureprojects/lein01/lib/clojure-1.2.1.jar> 
#<File /Users/gene/clojureprojects/lein01/lib/java.classpath-0.2.0.jar> 
[...details deleted])
$

Wrap up

Next step? You probably want a good editor. Lispers have used the Emacs for decades. You should give it a try. Here is how.

Resources

Books

Posted in Clojure | Tagged , | 1 Comment

JAXB parent-child cycle problem

I wrote a RESTful web service that was the backend for an iOS app. Since I wrote the the web administration app using Spring @MVC 3, I used that for the web service also instead of Jersey. One cool thing is how transparent the marshaling of your objects is. Here is a simple example.

1
2
3
4
5
@RequestMapping("/{familyName}/{givenName}")
public @ResponseBody
Composer findComposerByFullName(@PathVariable String familyName, @PathVariable String givenName) {
   return composerService.findComposerByFullName(familyName,givenName));
}

Notice what you don’t see. (How’s that for a sentence? :) ) The web service simply returns a Composer object (obtained from the composerService) – you don’t see any gyrations for returning XML or JSON! If the client requests XML, then Spring will automatically use JAXB to convert your Java object into XML. If Jackson is on your classpath and the client requests JSON then Spring will automatically marshal your object into JSON. Neat. (You can use other marshaling technologies. See the Resources below.)

Unless you’re returning an object graph that includes a bidirectional parent/child relationship.

Here is an example parent which is also a JPA entity. One composer has many works, and a work belongs to one composer. It is not necessary that it be an Entity for the marshaling to work; it’s just that JPA bidirectional associations are commonly used along with the topic at hand.

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
@Entity
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Composer implements Serializable, Comparable<Composer> {
   private static final long serialVersionUID = 1L;
 
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;
 
   @Basic(optional = true)
   @XmlElement(name = "givenName", required = false)
   @Column(name = "givenName", length = 128)
   private String givenName;
 
   @Basic(optional = true)
   @XmlElement(name = "middleName", required = false)
   @Column(name = "middleName", length = 128)
   private String middleName;
 
   @Basic(optional = false)
   @XmlElement(name = "familyName", required = true)
   @Column(name = "familyName", length = 255)
   private String familyName;
 
   @OneToMany(cascade = CascadeType.ALL, mappedBy = "composer", targetEntity = Work.class, fetch = FetchType.EAGER)
   @XmlElementWrapper(name = "works")
   @XmlElement(name = "work", required = true)
   private Set<Work> works = new HashSet<Work>();

And here is an example child. Since the relationship is bidirectional, there is a reference to the composer.

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
@Entity
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Work implements Serializable {
 
   private static final long serialVersionUID = 1L;
 
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Basic(optional = false)
   @Column(name = "id", nullable = false)
   private Long id;
 
   @Basic(optional = false)
   @Column(name = "title")
   private String title;
 
   @Basic(optional = true)
   @Temporal(TemporalType.DATE)
   private Date completionDate;
 
   @Basic(optional = true)
   @Temporal(TemporalType.DATE)
   private Date premiereDate;
 
   @Basic(optional = true)
   @Column(name = "instrumentation")
   private String instrumentation;
 
   @ManyToOne(fetch = FetchType.EAGER, optional = false)
   @JoinColumn(name = "COMPOSER_ID")
   private Composer composer;

Let’s set up a simple object graph with a Composer who has one Work. Then marshal the composer to
stdout with a small helper class.

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
public class JAXBhelper {
   private Logger logger = LoggerFactory.getLogger(this.getClass());
   public static void main(String[] args) {
      Composer composer = new Composer("Ludwig", "Beethoven");
      composer.setId(123L);
      composer.setOtherName("van");
      composer.setBirthDate(new Date());
      composer.setDeathDate(new Date());
 
      Work work = new Work();
      work.setPremiereDate(new Date());
      work.setCompletionDate(new Date());
      work.setInstrumentation("kazoo, comb, wax paper");
      work.setTitle("Cacophony No. 2.718");
      composer.add(work);
      JAXBhelper helper = new JAXBhelper();
      helper.xmlToStdout(composer);
   }
 
   public void xmlToStdout(Composer composer) {
      try {
         JAXBContext jaxbc = JAXBContext.newInstance(Composer.class);
         Marshaller marshaller = jaxbc.createMarshaller();
         marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
         marshaller.marshal(composer, System.out);
      } catch (JAXBException e) {
         e.printStackTrace();
         this.logger.error(e.getMessage());
      }
   }
}

Oops. It looks like JAXB doesn’t like this. This what the program barfs:

1
2
3
4
5
6
7
8
9
javax.xml.bind.MarshalException
 - with linked exception:
[com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: Composer  id:'123' ln:'Beethoven' gn:'Ludwig' mn:'null'
	Cacophony No. 2.718
 -> com.rockhoppertech.marshal.domain.Work@e78ffa9d id=null title='Cacophony No. 2.718' cid=123 -> Composer  id:'123' ln:'Beethoven' gn:'Ludwig' mn:'null'
	Cacophony No. 2.718
]
	at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:318)
etc.

The XML solution

To fix the XML the solution is fairly simple. Just mark the parent as @XmlTransient.

1
2
3
4
5
@ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "COMPOSER_ID")
// prevent a cycle
@XmlTransient
private Composer composer;

You now get this output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<composer>
    <id>123</id>
    <givenName>Ludwig</givenName>
    <familyName>Beethoven</familyName>
    <works>
        <work>
            <title>Cacophony No. 2.718</title>
            <completionDate>2011-10-26T17:28:04.994-04:00</completionDate>
            <premiereDate>2011-10-26T17:28:04.994-04:00</premiereDate>
            <instrumentation>kazoo, comb, wax paper</instrumentation>
        </work>
    </works>
</composer>

The Java solution

What about your Java object graph when this XML is deserialized? There is no attribute in the work element for the composer. But, you see that the work is a child element of composer so that information is not lost. Let’s check what happens with a few more utility methods.

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
public void saveXML(Composer composer, String fileName) {
   try {
      JAXBContext jaxbc = JAXBContext.newInstance(Composer.class);
      Marshaller marshaller = jaxbc.createMarshaller();
      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
      OutputStream out = null;
      try {
         out = new FileOutputStream(fileName);
         marshaller.marshal(composer, out);
      } catch (FileNotFoundException e) {
         e.printStackTrace();
      } finally {
         try {
            out.close();
         } catch (IOException e) {
         }
      }
   } catch (JAXBException e) {
      e.printStackTrace();
      this.logger.error(e.getMessage());
   }
}
 
public Composer readComposerXML(String fileName) {
   Composer composer = null;
   try {
      JAXBContext jaxbc = JAXBContext.newInstance(Composer.class);
      Unmarshaller marshaller = jaxbc.createUnmarshaller();
      InputStream file = null;
      try {
         file = new FileInputStream(fileName);
         composer = (Composer) marshaller.unmarshal(file);
      } catch (FileNotFoundException e) {
         e.printStackTrace();
         this.logger.error(e.getMessage());
      } finally {
         try {
            file.close();
         } catch (IOException e) {
         }
      }
 
   } catch (JAXBException e) {
      e.printStackTrace();
      this.logger.error(e.getMessage());
   }
   return composer;
}

Now in the main method let’s test the marshal/unmarshal process. We save the composer to a file and then read it back. Then we look at each work and print out the composer info.

1
2
3
4
5
6
7
...
helper.saveXML(composer, "composer.xml");
Composer read = helper.readComposerXML("composer.xml");
for(Work w: read.getWorks()) {
   System.out.println("Title: " + w.getTitle());
   System.out.println("Composer: " + w.getComposer());
}

Here is the output.

1
2
Title: Cacophony No. 2.718
Composer: null

Since the composer field in Work has been annotated as being XmlTransient, it is not restored on unmarshal. We have to do that ourselves. Luckily it is simple. Just implement this method in the child class (Work) to restore the reference to the parent.

1
2
3
public void afterUnmarshal(Unmarshaller u, Object parent) {
   this.composer = (Composer) parent;
}

Eye candy

You can specify the ordering of the elements output by JAXB with the XmlType annotation.

1
2
3
4
@XmlType(name = "ComposerType", propOrder = { "id", "familyName", "givenName",
		"middleName", "works" })
public class Composer implements Serializable, Comparable<Composer> {
...

Resources

JAXB home page

Spring MVC RequestBody annotation and converters

Download a Maven project for the code contained in this (and other) articles. Run the web service first via jetty:run, and then run the unit tests. For the simple raw test, simply right JAXBHelper as a Java application.

You probably might also know that if you have curl installed you can do this:

1
2
3
curl -i -H "Accept: application/xml" http://localhost:8080/jaxbmarshal/composerws/Mozart/Wolfgang/Amadeus
 
curl -i -H "Accept: application/json" http://localhost:8080/jaxbmarshal/composerws/composers
Posted in REST, XML | Tagged , | Leave a comment

Modify heap size for Eclipse on OSX

I recently tried to load a sample IRS 1120 tax return in XML format (Example_TransmissionWithConsolidatedReturn.xml in their schema distribution) into the Eclipse XML editor. I shouldn’t need to tell you that the IRS schemas are quite complex. The “simple” return was over 12MB! After a lot of heaving, Eclipse just punted and complained about running out of heap space. So, how do you increase the amount of heap space that Eclipse uses? This is different from the amount of heap space that your application uses.

On Linux and that Microsoft virus Petri dish you simply edit eclipse.ini in the Eclipse installation directory.
For OSX it is slightly more complex. In the Finder navigate to the Eclipse installation. You will see Eclipse.app. Right click on the app and choose “Show package contents”.

Then navigate to Contents/MacOS to find eclipse.ini. Now you can edit your preferred heap size. In my case I increased the max heap size: -Xmx1024m

Posted in Eclipse | Tagged , | Leave a comment

JDK 7 for OSX

In the past, developers obtained the JDK from Apple. Apple has announced that they will no longer provide a JDK. They will support the Open JDK project instead.
Here are he details of the Mac OS X Port Project within OpenJDK.

You can check status of the port on the project’s Wiki

There is also a mailing list.

You can download the builds.

Here are a few notes on the installation process.

Essentially, you need to mount the dmg and then drag and drop to install the JDK. The directory /Library/Java/JavaVirtualMachines need to already exist. If you’ve installed a previous version of the JDK, then it does.

Then run the OSX Java Preferences App to set which JDK you’d like to use. Your previous JDK is probably still at the top of the list. Simply drag and drop the new JDK to the top.

Posted in Java | Tagged , | Leave a comment
  • About the Author

    Gene De Lisa Development notes by Gene De Lisa on topics relating to Java and various open source projects such as the Spring Framework, Hibernate and others.

  • Recent Posts

  • Categories

  • Archives