Search

September 22, 2013

RESTful Web Services on JBossAS 7

Deploying RESTful web services on JBossAS 7 is relatively painless and straightforward. Thanks to the extensive support of annotations vs configuration in JEE 6, creating RESTful web services is almost XML-free.

Let’s start with the good stuff: the code you want to run as a web service. We will use as an example a web service supplying random number as inspired by the classic xkcd comic:

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("random")
public class RandomResource
{
    @GET
    @Path("xkcd")
    public String xkcd()
    {
        // Chosen by a fair dice roll
        // Guaranteed to be random
        return "4";
    }
}

Here we have assigned paths both to the entire class and the specific method. That means the resource will be available at the path "random/xkcd" relative to the path for RESTful web services (more details on that to follow).

From here we can go in two directions: supply an implementation of javax.ws.rs.core.Application and let our web services be found automatically or configure a bunch of things in the web.xml. We’ll take the easy way:

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/jaxrs")
public class RestApplication extends Application
{
}

Note that we do not override the methods in javax.ws.rs.core.Application. The specification indicates that the default implementation will return empty sets; furthermore if an implementation of Application returns empty sets then all root resource classes and providers in the WAR will be included. Which is all a fancy way of saying that this is exactly what we want: the container will scan the WAR and find all the resource classes by annotation.

Notice we used the ApplicationPath annotation; this tells the container the root URI for RESTful web services. So our xkcd() method now has URI "jaxrs/random/xkcd" relative to the deployment of the WAR.

Finally we have the small piece of XML necessary. We need to include a web.xml file in order to indicate that we are using servlet version 3.0. If all we have are the RESTful web services in the application, the web.xml can be empty aside from the root element:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
             http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
</web-app>

OK, now we are ready to package it up and deploy to JBossAS 7. We are going to create a WAR file named rest.war. Just to refresh your memory, the Java classes are compiled and placed in the WEB-INF/classes directory and the web.xml is placed in the WEB-INF directory. You may also want to include a "Hello world" HTML file in the WAR as well for debugging.

Copy the rest.war file into the $JBOSS_HOME/standalone/deployments directory and start JBoss if you have not already. Now you can get a random number (xkcd-style) via the URL http://localhost:8080/rest/jaxrs/random/xkcd.

Resources:

September 20, 2013

Trac Plugins vs Python Versions

Just a quick and dirty reminder than when you are trying to make Trac plugins work, you need to make sure you are using the correct python version of the egg.  For example, I recently wanted to add the RegexLinkPlugin to one of my Trac instances.  However, when I downloaded the provided egg and installed it, nothing happened.  Upon further inspection, I realized that the egg was for Python 2.5 and I was running 2.6 on my server.  I downloaded the source and recompiled and was rewarded with a (working) Python 2.6 egg.

By the way, the RegexLinkPlugin is a nice way to map wiki text to links in your Trac installation.  For example, I am using it on an IT wiki where each server has its own page but the servers are all referred to by all lowercase hostnames.  Using RegexLinkPlugin I can map each hostname to link to the page for the server with no extra wiki markup, ensuring my users always generate the links when using the hostnames.

September 8, 2013

Joining a CentOS server to Active Directory

As the number of CentOS (or Red Hat) machines in your environment grows, you begin to appreciate the need for a central login mechanism. Most workplaces already have a such a login for their Windows workstations in the form of an Active Directory domain. By joining your CentOS machines to the Active Directory domain, you allow users to login with the same credentials as on their Windows machines. Furthermore you do not need to add or remove users when new people join the team or others drop off the team.

For the purposes of these instructions, we will assume the Active Directory server is ad.example.com and the CentOS server is centos.example.com.

As a first step, we install Samba, kerberos and ntpd.

yum install samba samba-client samba-winbind krb5-workstation ntp
chkconfig smb on
chkconfig nmb on
service smb start
service nmb start
chkconfig ntpd on
ntpdate ad.example.com

We need to install and configure an ntp client because the Kerberos protocol will not work if the two machines' clocks are too far apart. Edit /etc/ntpd.conf remove all the existing server lines and replace them with:

server ad.example.com

Next start the ntpd server with the command service ntpd start.

I have had issues down the road with trying to synchronize the clocks against the Active Directory server; in those cases I configured the two servers to synchronize against the same third-party server. I suspect the problem was a (presumably temporary) issue with time.windows.com, the default Windows NTP server.

Next we want to be able to refer to machines on the local network using their short names. Add the following to /etc/resolv.conf:

search example.com

This may not be necessary depending on how the networking of the CentOS server is configured. For example, I have set up all the servers to use DHCP with reservations to keep all the IP configuration in one place. The DHCP server already is configured to have the clients append .example.com to bare host names so the /etc/resolv.conf already contains this line.

If you are using a static IP, add the following to /etc/hosts, replacing the IP address with the IP for your CentOS server and the host names with the proper values:

192.168.0.10    centos  centos.example.com

If you do not have a static IP, add the host entries to the existing line for localhost (127.0.0.1).

Next we need to configure Kerberos by making the following edits to /etc/krb5.conf (note that the capitalization is important in this file):

[libdefaults]
 default_realm = EXAMPLE.COM
 dns_lookup_realm = true
 dns_lookup_kdc = true
 allow_weak_crypto = yes

[realms]
 EXAMPLE.COM = {
  default_domain = example.com
  kdc = ad.EXAMPLE.COM
  admin-server = ad.EXAMPLE.COM
 }

[domain_realm]
 .example.com = EXAMPLE.COM
 example.com = EXAMPLE.COM

Next edit /etc/samba.conf, changing or adding the following:

# Use the value of your workgroup/domain here
workgroup = MY_WORKGROUP
password server = ad.example.com
realm = EXAMPLE.COM
security = ads
winbind use default domain = true
winbind offline logon = false
encrypt passwords = yes

Finally we can initialize Kerberos and join the domain. You will need the credentials of a user allowed to make changes in the domain (in the example we use administrator).

kinit administrator@EXAMPLE.COM
# Enter the password at the prompt and expect no other output
authconfig --update \
           --kickstart \
           --enablewinbind \
           --enablewinbindauth \
           --smbsecurity=ads \
           --smbrealm=EXAMPLE.COM \
           --winbindjoin=administrator@EXAMPLE.COM \
           --winbindtemplatehomedir=/home/%U \
           --winbindtemplateshell=/bin/bash \
           --enablewinbindusedefaultdomain \
           --enablelocauthorize \
           --smbservers=ad.example.com \
           --enablemkhomedir

At this point you should be done. You should be able to log into the machine using your Windows credentials.

Once in a while I find it is necessary to re-join the domain. Use the following commands:

# Restart all relevant services
service smb stop
service nmb stop
service winbind stop
service winbind start
service nmb start
service smb start
# Re-join the domain
net ads join -S ad.example.com -U administrator
# Restart winbind
service winbind stop
service winbind start
# Test the credentials
wbinfo -t
# List the users
wbinfo -u

If the last two tests do not come out the way you expect (wbinfo -t should report success and wbinfo -u should list all the users in your domain), you have some googling to do. Let me know how it turns out.