2abf5640c03d630e8aa30d8c21d875bc713e9c5d
[yaz4j-moved-to-github.git] / examples / zgate / doc / ARTICLE
1 BUILDING A SIMPLE HTTP-TO-Z3950 GATEWAY USING YAZ4J AND TOMCAT
2 ==============================================================
3
4 [Yaz4J](http://www.indexdata.com/yaz4j) is a wrapper library over the 
5 client-specific parts of YAZ, a C-based Z39.50 toolkit, and allows you to use 
6 the ZOOM API directly from Java. Initial version of Yaz4j has been written
7 by Rob Styles from [Talis][http://www.talis.com] and the project is now 
8 developed and maintained at IndexData.
9 [ZOOM](http://zoom.z3950.org/api/zoom-1.4.html) is a relatively straightforward 
10 API and with a few lines of code you can write a basic application that can 
11 establish connection to a Z39.50 server. 
12 Here we will try to build a very simple HTTP-to-Z3950 gateway using yaz4j and 
13 the Java Servlet technology.
14
15 ## COMPILING AND INSTALLING YAZ4J
16
17 Yaz4j is still an experimental piece of software and as such is not distributed
18 via Index Data's public Debian apt repository and there is no Windows installer
19 either. While it is possible to use the pre-built Linux binaries, users of 
20 other OSes will have compile yaz4j from source. No need to worry (yet) - the 
21 process of compiling yaz4j is quite simple and we will be up and running in no 
22 time :). The source code can be checked-out out from our 
23 [Git repository](http://git.indexdata.com/?p=yaz4j.git;a=summary), and assuming
24 you have Git installed on your machine you can do that with:
25
26     git clone git://git.indexdata.com/yaz4j
27
28 The compilation of both native and Java source code is controlled by Maven2, 
29 to build the library, invoke the following commands:
30
31     cd yaz4j
32     mvn install
33
34 That's it. If the build has completed successfully you end up with two files: 
35 os-independent jar archive with Java ZOOM API classes 
36 (yaz4j/any/target/yaz4j-any-VERSION.jar) and os-dependent shared library 
37 (yaz4j/linux/target/libyaz4j.so or yaz4j/win32/target/yaz4j.dll) that contains 
38 all necessary JNI "glue" to make the native calls possible from Java. If we were
39 writing a command line Java application, like any other external Java library, 
40 yaz4j-any-VERSION.jar would have to be placed on your application classpath 
41 and the native, shared library would have to be added to your system shared 
42 library path (LD_LIBRARY_PATH on linux, PATH on Windows) or specified as a 
43 Java system property (namely the java.library.path) just before your 
44 application is executed:
45
46     java -cp /path/to/yaz4j-*.jar -Djava.library.path=/path/to/libyaz4j.so MyApp
47
48 ## SETTING UP THE DEVELOPMENT ENVIRONMENT
49
50 Setting up a development/runtime environment for a web (servlet) application is 
51 a bit more complicated. First, you are not invoking the JVM directly, but the 
52 servlet container (e.g Tomcat) run-script is doing that for you. At this 
53 point the shared library (so or dll) has to be placed on the servlet container's
54 shared libraries load path. Unless your library is deployed to the standard 
55 system location for shared libs (/usr/lib on Linux) or it's location is already 
56 added to the path, the easiest way to do this in Tomcat is by editing 
57 (create it if it does not exist) the CATALINA_HOME/bin/setenv.sh (setenv.bat on
58 Windows) script and putting the following lines in there:
59
60     LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/libyaz4j.so
61     export LD_LIBRARY_PATH
62
63 on Windows
64
65      set PATH=%PATH;X:\path\to\yaz4j.dll
66
67 That's one way of doing it, another would be to alter the standard set of
68 arguments passed to the JVM before the Tomcat starts and add 
69 -Djava.library.path=/path/to/lib there. Depending on a situation this might be 
70 preferable/easier (on Debian/Ubuntu you can specify JVM arguments using 
71 /etc/default/tomcat6).
72
73 With the shared library installed we need to install the pure-Java yaz4j-any*jar
74 with ZOOM API classes by placing it in Tomcat's `lib` directory 
75 (CATALINA_HOME/lib). As this library makes the Java System call to load the 
76 native library into the JVM you cannot simply package it along with your web
77 application (inside the .war file) - it would try to load the library each time
78 you deploy the webapp and all consecutive deployments would fail.
79
80 ## WRITING A SERVLET-BASED GATEWAY
81
82 With your servlet environment set up all that is left is to write the actual 
83 application (peanuts :)). At IndexData we use Maven for managing builds of our 
84 Java software components but Maven is also a great tool for quickly starting up 
85 a project. To generate a skeleton for our webapp use the Maven archetype plugin:
86
87     mvn -DarchetypeVersion=1.0.1 -Darchetype.interactive=false -DarchetypeArtifactId=webapp-jee5 -DarchetypeGroupId=org.codehaus.mojo.archetypes -Dpackage=com.indexdata.zgate -DgroupId=com.indexdata -DartifactId=zgate archetype:generate --batch-mode
88
89 This will generate a basic webapp project structure:
90
91 |-- pom.xml
92 `-- src
93 |-- main
94 |   |-- java
95 |   |   `-- com
96 |   |       `-- indexdata
97 |   |           `-- zgate
98 |   `-- webapp
99 |       |-- WEB-INF
100 |       |   `-- web.xml
101 |       `-- index.jsp
102 `-- test
103     `-- java
104         `-- com
105             `-- indexdata
106                 `-- zgate
107
108 Maven has already added basic JEE APIs for web development as the project 
109 dependencies, we need to do the same for yaz4j, so edit the `pom.xml` and 
110 add the following lines in the `dependencies` section:
111
112 <dependency>
113   <groupId>org.yaz4j</groupId>
114   <artifactId>yaz4j-any</artifactId>
115   <version>VERSION</version>
116   <scope>provided</scope>
117 </dependency>
118
119 It's crucial that the scope of this dependency is set to `provided` otherwise
120 the library would end up packaged in the .war archive and we don't want that.
121
122 The implementation of our simple gateway will be contained in a single servlet -
123 `ZGateServlet` - which we need to place under `src/main/webapp/com/indexdata/zgate.` 
124 The gateway will work by answering HTTP GET requests and will be controlled 
125 solely by HTTP parameters, the servlet doGet method is shown below:
126
127 protected void doGet(HttpServletRequest request, HttpServletResponse response)
128   throws ServletException, IOException {
129     String zurl = request.getParameter("zurl");
130     if (zurl == null || zurl.isEmpty()) { response.sendError(400, "Missing parameter 'zurl'"); return; }
131
132     String query = request.getParameter("query");
133     if (query == null || query.isEmpty()) { response.sendError(400, "Missing parameter 'query'"); return; }
134
135     String syntax = request.getParameter("syntax");
136     if (syntax == null || syntax.isEmpty()) { response.sendError(400, "Missing parameter 'syntax'"); return; }
137
138     int maxrecs=10;
139     if (request.getParameter("maxrecs") != null && !request.getParameter("maxrecs").isEmpty()) {
140       try {
141         maxrecs = Integer.parseInt(request.getParameter("maxrecs"));
142       } catch (NumberFormatException nfe) {
143         response.sendError(400, "Malformed parameter 'maxrecs'");
144         return;
145       }
146     }
147
148     response.getWriter().println("SEARCH PARAMETERS");
149     response.getWriter().println("zurl: " + zurl);
150     response.getWriter().println("query: " + query);
151     response.getWriter().println("syntax: " + syntax);
152     response.getWriter().println("maxrecs: " + maxrecs);
153     response.getWriter().println();
154
155     Connection con = new Connection(zurl, 0);
156     con.setSyntax(syntax);
157     try {
158       con.connect();
159       ResultSet set = con.search(query, Connection.QueryType.PrefixQuery);
160       response.getWriter().println("Showing " + maxrecs + " of " +set.getSize());
161       response.getWriter().println();
162       for(int i=0; i<set.getSize() && i<maxrecs; i++) {
163         Record rec = set.getRecord(i);
164         response.getWriter().print(rec.render());
165       }
166     } catch (ZoomException ze) {
167       throw new ServletException(ze);
168     } finally {
169       con.close();
170     }
171   }
172
173
174 With the code in-place we can try to compile the project:
175
176     mvn compile
177
178 If all is OK, the next step is to register our servlet and map it to an URL in 
179 src/main/webapp/WEB-INF/web.xml:
180
181 <servlet>
182   <servlet-name>ZgateServlet</servlet-name>
183   <servlet-class>com.indexdata.zgate.ZgateServlet</servlet-class>
184 </servlet>
185 <servlet-mapping>
186   <servlet-name>ZgateServlet</servlet-name>
187   <url-pattern>/zgate</url-pattern>
188 </servlet-mapping>
189
190 On top of that, we will also  make sure that our servlet is automatically 
191 triggered when accessing the root path of our application:
192
193 <welcome-file-list>
194   <welcome-file>zgate</welcome-file>
195   <welcome-file>index.jsp</welcome-file>
196 </welcome-file-list>
197
198 Now we are ready to build our webapp:
199
200   mvn package
201
202 The resulting .war archive is located under `target/zgate.war`, we can deploy
203 it on tomcat (e.g by using the /admin Tomcat admin  console) and test by issuing
204 the follwoing request with your browser or curl 
205 (assuming Tomcat is running on localhost:8080):
206
207   http://localhost:8080/zgate/?zurl=z3950.loc.gov:7090/voyager&query=@attr%201=7%200253333490&syntax=usmarc
208
209   
210 That's it! You just build yourself a HTTP-to-Z3950 gateway! Just be careful
211 with exposing it to the outside world - it's not very secure and could be easily
212 exploited. The source code and the gateway's Maven project is available in the
213 yaz4j repository under examples/zgate. In the meantime, IndexData is working on a Debian/Ubuntu package (and maybe even a Windows installer :)) so installation of yaz4j and Tomcat configuration will be greatly simplified - so stay tuned!