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