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.

August 18, 2013

Production Mail server for AS

In the past I have detailed how I set up a mail server for development application servers. Today I want to explain how to set up a mail server for a production application server, or one that should interact with real-life mail systems.

The goal for this configuration is to create a mail server that only allows connections from localhost and can relay mail from localhost to other, real mail domains.

In this example I have configured the application server to send mail from a subdomain, mail.example.com. In the examples I am using 93.184.216.119 as the public IP for mail.example.com. You should replace these with your own values.

Once again we are working on a CentOS 6 installation. Postfix was already installed.

  1. Configure postfix
    1. Make the following configuration changes to /etc/postfix/main.cf:
      myhostname = mail.example.com
      # Use the public IP of mail.example.com
      proxy_interfaces = 93.184.216.119
      # Only accept mail from localhost
      mynetworks_style = host
      # Never forward mail from strangers
      relay_domains =

      See the postfix documentation at http://www.postfix.org/BASIC_CONFIGURATION_README.html if you need more information about any of these.

    2. Refresh postfix using the following commands:
      postfix reload
      service postfix restart
  2. Next configure your application server to use localhost as the mail server. Refer to your application server documentation for details.
  3. Configure your router and/or firewall
    1. Configure the router to use public IP 93.184.216.119 when the application server machine accesses the internet on port 25.
    2. If necessary, configure your router so that the application server is allowed to connect to your internal company mail server on port 25. This was necessary in my case because the application server and the company mail server were on separate subnets (DMZ-type configuration).
  4. Configure DNS
    1. Make sure the MX records for the domain includes the IP for mail.example.com, in this case 93.184.216.119.
    2. If an SPF record for the domain does not already exist, add one by creating a TXT record containing v=spf1 mx -all.

      If you already have an SPF record, you’ll have to figure out if it needs to be modified to accommodate the new server. You can test it by sending mail to a GMail account from the application server. Then view the raw source of the email, GMail will have added headers to the mail indicating whether SPF passed or not. You can also use this SPF validator. If you need to tweak the record, refer to the documentation at http://www.openspf.org/.

At this point you should have a production application server happily sending mail to any address.

July 28, 2013

Ubuntu 12.10: Installing taglib-ruby

Back when I was fooling around with Ruby I wrote some code using the ruby-taglib library for manipulating mp3 tags. Later, I went through the process of installing Ubuntu 12.10 from scratch on my main system. Eventually I tried running the old on the new system and discovered that I needed to re-install ruby-taglib and ran into some difficulties.

I’ll start with the punchline for people just looking to get on with it:

$ sudo apt-get install ruby-dev libstdc++6-4.7-dev libtag1-dev g++
$ sudo gem install taglib-ruby libtag1-dev

What follows is all the gory details with the error messages in the hope that helps the poor soul using Google to solve this issue. First, I tried it with none of the above packages installed:

$ sudo gem install taglib-ruby
Building native extensions.  This could take a while...
ERROR:  Error installing taglib-ruby:
 ERROR: Failed to build gem native extension.

        /usr/bin/ruby1.9.1 extconf.rb
/usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in `require': cannot load such file -- mkmf (LoadError)
 from /usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
 from /var/lib/gems/1.9.1/gems/taglib-ruby-0.5.2/ext/extconf_common.rb:5:in `<top (required)>'
 from /usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
 from /usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
 from extconf.rb:2:in `<main>'


Gem files will remain installed in /var/lib/gems/1.9.1/gems/taglib-ruby-0.5.2 for inspection.
Results logged to /var/lib/gems/1.9.1/gems/taglib-ruby-0.5.2/ext/taglib_base/gem_make.out

Next I tried installing the just the ruby-dev package:

$ sudo apt-get install ruby-dev
$ sudo gem install taglib-ruby
Building native extensions.  This could take a while...
ERROR:  Error installing taglib-ruby:
 ERROR: Failed to build gem native extension.

        /usr/bin/ruby1.9.1 extconf.rb
checking for main() in -lstdc++... no
You must have libstdc++ installed.
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers.  Check the mkmf.log file for more
details.  You may need configuration options.

Provided configuration options:
 --with-opt-dir
 --with-opt-include
 --without-opt-include=${opt-dir}/include
 --with-opt-lib
 --without-opt-lib=${opt-dir}/lib
 --with-make-prog
 --without-make-prog
 --srcdir=.
 --curdir
 --ruby=/usr/bin/ruby1.9.1
 --with-tag-dir
 --without-tag-dir
 --with-tag-include
 --without-tag-include=${tag-dir}/include
 --with-tag-lib
 --without-tag-lib=${tag-dir}/lib
 --with-stdc++lib
 --without-stdc++lib


Gem files will remain installed in /var/lib/gems/1.9.1/gems/taglib-ruby-0.5.2 for inspection.
Results logged to /var/lib/gems/1.9.1/gems/taglib-ruby-0.5.2/ext/taglib_base/gem_make.out

This result was confusing because libstdc++ was installed on the system. I realized I needed the libstdc++ development package:

$ apt-cache search stdc++ | grep dev
lib32gmp-dev - Multiprecision arithmetic library developers tools (32bit)
libgmp-dev - Multiprecision arithmetic library developers tools
libstdc++6-4.4-dev - GNU Standard C++ Library v3 (development files)
libstdc++6-4.6-dev - GNU Standard C++ Library v3 (development files)
libstdc++6-4.7-dev - GNU Standard C++ Library v3 (development files)
libstdc++6-4.5-dev - GNU Standard C++ Library v3 (development files)
libstdc++6-4.6-dev-armel-cross - GNU Standard C++ Library v3 (development files)
libstdc++6-4.6-dev-armhf-cross - GNU Standard C++ Library v3 (development files)
libstdc++6-4.7-dev-armel-cross - GNU Standard C++ Library v3 (development files)
libstdc++6-4.7-dev-armhf-cross - GNU Standard C++ Library v3 (development files)
$ sudo apt-get install libstdc++6-4.7-dev
$ sudo gem install taglib-ruby
Building native extensions.  This could take a while...
ERROR:  Error installing taglib-ruby:
 ERROR: Failed to build gem native extension.

        /usr/bin/ruby1.9.1 extconf.rb
checking for main() in -lstdc++... yes
checking for main() in -ltag... no
You must have taglib installed in order to use taglib-ruby.

Debian/Ubuntu: sudo apt-get install libtag1-dev
Fedora/RHEL: sudo yum install taglib-devel
Brew: brew install taglib
MacPorts: sudo port install taglib

*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers.  Check the mkmf.log file for more
details.  You may need configuration options.

Provided configuration options:
 --with-opt-dir
 --with-opt-include
 --without-opt-include=${opt-dir}/include
 --with-opt-lib
 --without-opt-lib=${opt-dir}/lib
 --with-make-prog
 --without-make-prog
 --srcdir=.
 --curdir
 --ruby=/usr/bin/ruby1.9.1
 --with-tag-dir
 --without-tag-dir
 --with-tag-include
 --without-tag-include=${tag-dir}/include
 --with-tag-lib
 --without-tag-lib=${tag-dir}/lib
 --with-stdc++lib
 --without-stdc++lib
 --with-taglib
 --without-taglib


Gem files will remain installed in /var/lib/gems/1.9.1/gems/taglib-ruby-0.5.2 for inspection.
Results logged to /var/lib/gems/1.9.1/gems/taglib-ruby-0.5.2/ext/taglib_base/gem_make.out

And the next piece was the taglib development package:

$ sudo apt-get install libtag1-dev
$ sudo gem install taglib-ruby
Building native extensions.  This could take a while...
ERROR:  Error installing taglib-ruby:
 ERROR: Failed to build gem native extension.

        /usr/bin/ruby1.9.1 extconf.rb
checking for main() in -lstdc++... yes
checking for main() in -ltag... yes
creating Makefile

make
compiling taglib_base_wrap.cxx
make: g++: Command not found
make: *** [taglib_base_wrap.o] Error 127


Gem files will remain installed in /var/lib/gems/1.9.1/gems/taglib-ruby-0.5.2 for inspection.
Results logged to /var/lib/gems/1.9.1/gems/taglib-ruby-0.5.2/ext/taglib_base/gem_make.out

And finally we need g++ (how else can we compile C++?):

$ sudo apt-get install g++
$ sudo gem install taglib-ruby
Building native extensions.  This could take a while...
Successfully installed taglib-ruby-0.5.2
1 gem installed
Installing ri documentation for taglib-ruby-0.5.2...
Installing RDoc documentation for taglib-ruby-0.5.2...

July 21, 2013

Conditional Table Creation for Oracle

A quick example script for creating a table in an Oracle database only when it does not already exist.

declare
    objExists number;
    theSql varchar2(4000);
begin
    begin
        select 1 into objExists from USER_TABLES
            where TABLE_NAME = 'MY_NEW_TABLE';
        exception
            when no_data_found then
            null;
    end;
    if (objExists is null)
    then
        theSql := 'create table MY_NEW_TABLE
                   (
                       MY_NEW_TABLE_ID number(38),
                       A_VARCHAR varchar2(1000)
                   )';
        execute immediate theSql;
    end if;

    -- We can also add constraints conditionally
    begin
        select 1 into objExists from USER_CONSTRAINTS
            where CONSTRAINT_NAME = 'MY_NEW_TABLE_PK';
        exception
            when no_data_found then
            null;
    end;
    if (objExists is null)
    then
        theSql := 'alter table MY_NEW_TABLE
                       add constraint MY_NEW_TABLE_PK
                       primary key (MY_NEW_TABLE_ID)';
        execute immediate theSql;
    end if;

Hat tip to Vance Duncan.

June 9, 2013

Additional AJP connectors within SELinux environment

I recently went through the exercise of adding an additional JBoss application server to a production CentOS 6.4 server. The two applications were to be hosted on the same machine using virtual name servers to distinguish requests. I have covered virtual name servers before in my post on install Trac on CentOS 6. Multiple instances of JBoss can be made to play nice on the same servers by shifting the ports via the switch ‑Djboss.service.binding.set=ports‑01.

So, with everything configured we should be done, right? Not so fast. Unfortunately I thought I had everything done but I got a 503 error, Service Temporarily Unavailable, when accessing the server via Apache. Directly accessing the JBoss server worked, so the problem had to be with Apache accessing the AJP connectors.

The connectors were configured thusly:

    ProxyPassReverseCookiePath /xyzzy /
    # Reverse proxy everything under /xyzzy
    ProxyPass /xyzzy/ ajp://prodas01:8109/xyzzy/
    ProxyPassReverse /xyzzy/ ajp://prodas01:8109/xyzzy/

Note that we are using port 8109 instead of 8009 since all the JBoss ports have been shifted by 100. Checking the Apache error log I saw the following:

[Wed Jun 05 13:11:55 2013] [error] (13)Permission denied: proxy: AJP: attempt to connect to 10.0.0.2:8109 (prodas01) failed

The problem did not appear to be firewall related since no firewall changes were necessary to get Apache talking to the first JBoss server on port 8009. Further probing revealed the following entry in the audit.log:

type=AVC msg=audit(1370452315.660:2053): avc: denied { name_connect } for pid=27314 comm="httpd" dest=8109 scontext=unconfined_u:system_r:httpd_t:s0 tcontext=system_u:object_r:port_t:s0 tclass=tcp_socket

So the problem turned out to be SELinux. Apparently SELinux puts restrictions on what ports the httpd server can contact. The following shows the configuration out of the box:

[root@prodas01 /] semanage port -l | grep -w http_port_t
http_port_t                    tcp      80, 443, 488, 8008, 8009, 8443

So port 8009 is allowed, but port 8109 is not. We need to add it to the list:

[root@prodas01 /] semanage port -a -t http_port_t -p tcp 8109
[root@prodas01 /] semanage port -l | grep http_port_t
http_port_t                    tcp      8109, 80, 443, 488, 8008, 8009, 8443

Hat tip to krow oak for this helpful forum posting.