Connecting to HP ALM via Java is a bit of a cumbersome task when you have never worked with a REST API before.
It is for certain not as easy as connecting to a database using a connector provided by any vendor.
Using the HP ALM REST API Examples in version 11.52
In order to use the HP ALM REST API Examples in Eclipse you will have to apply changes to some classes and even rewrite some of the tests.
I like to have simple examples, which are not hard to understand for beginners. In contrary HP provides you with an examples section, which is not easy to understand or use due to missing explanations as well as overall complexity.
First you should understand what is happening in the background when you will use the little API HP provided. The following article might help to give you an idea.
Connect to HP ALM via browser using REST API
Anyhow, let's start by setting up an example eclipse project.
Prepare HP ALM REST example project in eclipse
Create a new Java Project in eclipse called "almConnector" (or whatever you like - it does not matter).
In the src directory of this new eclipse project create three new packages.
Now go to the HP ALM REST API reference in your ALM documentation library.
To do so when looged into a HP ALM project choose Help -> Documentation Library from the top menu bar.
Then select the section API Reference and in there you can find the REST API Reference.
Within the REST API Reference you can find an examples section. Open it and expand the directory infrastructure.
Within this directory infrastructure you have all classes you need in order to connect to HP ALM and more using the HP ALM REST API. As its name says, it will provide you with an infrastructure for operating ALM from within Java.
Now for the painful part. Copy paste the source code for each class listed in the infrastructure example directory into resembling Java classes in the infrastructure package in your new eclipse example project. In order to compile each new class, you have to change the package names from the example section to "infrastructure", but eclipse will tell you to do so anyway.
The end result should look as displayed in the following screenshot.
I tried to figure out the minimum set of classes you will need to get somewhere using the examples for reading an entity provided by HP and this is it.
Unfortunately I cannot publish the source code here for obvious reasons. Anyhow, if you have access to an HP ALM server you can apply the above actions and you have everything you need to start coding your own examples and other things.
EDIT: I found a freely available version of the HP ALM REST API documentation on the HP website. Here is the link, but be careful since it is marked as a technical preview. However, if I may speculate the API has reached a very stable state and I do not think they are going to apply massive changes.
http://alm-help.saas.hpe.com/de/12.50/api_refs/REST_TECH_PREVIEW/ALM_REST_API_TP.html
However the infrastructure classes provide you with a small backbone to apply REST calls to HP ALM, you have to adjust them and the example classes in order for them to work properly. Unfurtunately the documentation provided by HP leaves you in the dark regarding this matter and requires you to test, debug and swear (in that particular order) until you finally find a solution.
Don't worry, I'll present you with my workarounds, code adjustments and insights straight away instead of leaving you dead dry behind just shooting at you with source code not putting it into any context.
First let me explain what each of the infrastructure classes does.
Class Name |
Description |
Is Adjustment Necessary? |
Base64Encoder |
Providing Base64 encoding for arrays of simple type byte. In the examples it is used to encode a users name as well as his password into a Base64 String before sending the login information to the server. As we Germans say: "Gut gemacht!". I am not a security expert, but anyway better than sending plain text. |
|
Constants |
Providing fix values for example for host (server), domain as well as project name. They also expect you to save a password for an HP ALM user in the class constants in plain text, which I cannot recommend. Instead use a simple input dialog. |
Yes |
Entity |
This class describes a complex xml type called Entity. An entity can be any entity in ALM since each entity is delivered using the same xml schema definition. You can read a defect or a test or a requirement into the Entity class using the EntityMarshallingUtils class, which uses the java api jabx to convert xml to java objetcs and vise versa. This class contains two nested classes Fields as well as Field, which I will not expicilty list here. |
|
EntityDescriptor |
A class used to provide meta data for entities in ALM. |
|
EntityMarshallingUtils |
A wrapper around two basic methods provided by jaxb marshal and unmarshal where this has been twisted around by the HP programmers. Actually marshal in this class calls the jaxb method to unmarshal an XML file into Java objects and unmarshal in this class calls marshal to convert Java objects into an XML file. |
|
Response |
This is a naive implementation of an HTTP response. |
|
RestConnector |
Provides a simple wrapper around a connection to HP ALM. This class is providing only static methods, thus beeing able to hold the state of a connection available for all other infrastructure classes. This class has to be adjusted for version HP ALM 12.0 or later. |
Yes, for HP ALM later than version 12.0 |
Using these classes and the HP ALM examples section and the ALM Login example in particular I derived a class named AlmConnector, which is easier to use than the HP example for logging into ALM.
Adjustments
First of all let's apply some simple adjustments to the class Constants. Open this class and adjust the following section to meet your project and host.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Constants {
private Constants() {
}
public static final String HOST = "https://{HOST}/qcbin";
/**
* The port is 443 for https or 80 for http.
*/
public static final String PORT = "443";
public static final String USERNAME = "{USER_NAME}";
public static final String PASSWORD = "Do NOT store any password in clear text in any kind of dokument in any business environment!";
public static final String DOMAIN = "{DOMAIN}";
public static final String PROJECT = "{PROJECT}";
..
Once you adjusted this class it should be possible to use the Authentication example from the HP ALM REST API documentation.
However, the Authentication example and therefore all other examples as provided in the documentation of the REST API did not work for me at one client. Root cause was that the login method of the authentication example expects the result to return an URL to authenticate against if a user is not yet authenticated. Basically the examples searches an URL in the provided result page and fails to find it.
Below a short description on how you have to adjust the login method to return a working url and remove the part searching for an URL to authenticate against, if you want to use the HP examples as they are.
In order to check, if you really are logged on to ALM, the login mehtod calls following url.
https://{host}/qcbin/rest/is-authenticated
The result will be a simple xml stating your user name, if you are successfully logged into ALM.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<AuthenticationInfo>
<Username>abolte</Username>
</AuthenticationInfo>
If you are not authenticated, the server will deliver the following result.
HTTP ERROR 401
Problem accessing /qcbin/rest/is-authenticated. Reason:
Authentication failed. Browser based integrations - to login append '?login-form-required=y to the url you tried to access.
Do not let this message confuse you to append?login-form-required=y to the provided url. At least for me using ALM version 11.52 it did not work.
Instead use the URL https://{host}/qcbin/authentication-point/authenticate as a return value for the login method of the authentication example class. For details please refer to the login and isAuthenticated methods of the class AlmConnector below.
Then at least the authentication example should work as expected. Other examples I did not test yet, since I am already fed up with the examples. But that is another story which begins in the next chapter called AlmConnector.
Adjustments for HP ALM 12.0 or later
I received feedback from other developers that my example does not work with HP ALM 12.0. Below you can find what one developer in particular shared with me. Thanks again for this feedback. It already helped others :0).
You will need to add the following method to the class RestConnector.java. Once you called the method login() from the class RestConnector you have to call this method getQcSession().
1
2
3
4
5
6
7
8
9
10
11
12
13
//added per KM01754222
public void getQCSession(){
String qcsessionurl = this.buildUrl("rest/site-session");
Map<String, String> requestHeaders = new HashMap<String, String>();
requestHeaders.put("Content-Type", "application/xml");
requestHeaders.put("Accept", "application/xml");
try {
Response resp = this.httpPost(qcsessionurl, null, requestHeaders);
this.updateCookies(resp);
} catch (Exception e) {
e.printStackTrace();
}
}
I did not test this myself yet, but two developers have independently confirmed it works. I've also updated my Unit Tests at the bottom of this article.
Thanks for this go to Cory Henderson, a Senior Support Engineer.
AlmConnector
Alternatively to using the REST API examples as they are you can use my class AlmConnector in order to create your own unit tests. This is much more efficient in the end since the HP examples do not apply single actions. For instance the example for reading a defect is creating a defect before reading it. Also it only shows how to read only a single defect and not a collection of defects.
Furthermore the REST API examples are implemented in a rather confusing way constructing some methods, which look complex but in fact are not.
For this reason and the first example not working properly, I implemented a new class called AlmConnector to provide basic functions like login and logout. This made it much easier for me to develop unit tests and other supporting classes like Readers and Writers.
Source Code
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/**
*
*/
package alm;
import infrastructure.Base64Encoder;
import infrastructure.Response;
import infrastructure.RestConnector;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.Map;
/**
* @author Alexander Bolte - Bolte Consulting 2015
* <p>
* AlmConnector is only a thin layer around the HP class RestConnector,
* which resides in the package infrastructure. AlmConnector provides
* only methods around logging into ALM.
* </p>
* <p>
* HP designed the class RestConnector in a static way. Once
* initialized, the class holds an instance, which can be referenced by
* any other class using the method
* <code>RestConnector.getInstance()</code>.
* </p>
* <p>
* Operations like reading from and writing to HP ALM have to be applied
* through the class RestConnector directly ignoring the AlmConnector.
* </p>
* <p>
* This class applies for HP ALM 11.52. It has not been tested for newer
* versions like 12.0.
* </p>
*/
public class AlmConnector {
/**
* <p>
* Initializes / prepares a new connection to HP ALM using the provided
* details. A connection to ALM is realized using the class
* infrastructure.RestConnector.
* </p>
* <p>
* In order to open a connection prepared using this constructor you have to
* call the method <code>login</code> from this class and provide a user
* name as well as the corresponding password.
* </p>
* <p>
* Therefore connecting to ALM would look as follows.
* </p>
* <code>
* AlmConnector alm = new AlmConnector(new HashMap<String, String>(), Constants.HOST, Constants.DOMAIN, Constants.PROJECT);
* <br/><br/>
* alm.login("userName", "HP ALM Password");
* </code>
*
* @param serverUrl
* - a String providing an URL following the format
* <code>"https://{HOST}/qcbin"</code>
* @param domain
* - a String providing the domain a user wants to log onto.
* @param project
* - a String providing the name of a project a user wants to log
* into.
*/
public AlmConnector(final String serverUrl, final String domain,
final String project) {
this.con = RestConnector.getInstance().init(
new HashMap<String, String>(), serverUrl, domain, project);
}
/**
* <p>
* Once you initialized the class RestConnector, you can use this
* constructor to create a new object from AlmConnector since the referenced
* class RestConnector is keeping the connection details.
* </p>
*/
public AlmConnector() {
this.con = RestConnector.getInstance();
}
/**
* <p>
* Attempts to log a user into an ALM project. If a user is already
* authenticated, no action is applied but true will be returned.
* </p>
* <p>
* Calling <code>login</code> after being already authenticated will not
* logout the currently logged in user. You specifically have to call
* <code>logout</code> before logging in with other user credentials.
* </p>
* <p>
* To check if a user is authenticated call method
* <code>isAuthenticated()</code>.
* </p>
*
* @param username
* - a String providing the name of a user in HP ALM.
* @param password
* - the HP ALM password corresponding a provided user name.
* @return true if user is successfully authenticated else false.
* @throws Exception
*/
public boolean login(String username, String password) throws Exception {
/**
* Get the current authentication status.
*/
String authenticationPoint = this.isAuthenticated();
/**
* If the authenticationPoint is null, the user is already
* authenticated. In this case no login necessary.
*/
if (authenticationPoint != null) {
return this.login(authenticationPoint, username, password);
}
return true;
}
/**
* <p>
* Logging into HP ALM is standard HTTP login (basic authentication), where
* one must store the returned cookies for further use.
* <p>
*
* @param loginUrl
* - a String providing an URL to authenticate at.
* @param username
* - an HP ALM user name.
* @param password
* - an HP ALM user password corresponding username.
* @return true if login is successful, else false.
* @throws Exception
*/
private boolean login(String loginUrl, String username, String password)
throws Exception {
/**
* create a string that looks like:
* "Basic ((username:password)<as bytes>)<64encoded>"
*/
byte[] credBytes = (username + ":" + password).getBytes();
String credEncodedString = "Basic " + Base64Encoder.encode(credBytes);
Map<String, String> map = new HashMap<String, String>();
map.put("Authorization", credEncodedString);
Response response = con.httpGet(loginUrl, null, map);
boolean ret = response.getStatusCode() == HttpURLConnection.HTTP_OK;
return ret;
}
/**
* Closes a session on a server and cleans session cookies on a client.
*
* @return true if logout was successful.
* @throws Exception
*/
public boolean logout() throws Exception {
/**
* note the get operation logs us out by setting authentication cookies
* to: LWSSO_COOKIE_KEY="" via server response header Set-Cookie
*/
Response response = con.httpGet(
con.buildUrl("authentication-point/logout"), null, null);
return (response.getStatusCode() == HttpURLConnection.HTTP_OK);
}
/**
* Indicates if a user is already authenticated and returns an URL to
* authenticate against if the user is not authenticated yet. Having this
* said the returned URL is always as follows.
* https://{host}/qcbin/authentication-point/authenticate
*
* @return null if a user is already authenticated.<br>
* else an URL to authenticate against.
* @throws Exception
* - an Exception occurs, if HTTP errors like 404 or 500 occur
* and the thrown Exception should reflect those errors.
*/
public String isAuthenticated() throws Exception {
String isAuthenticateUrl = con.buildUrl("rest/is-authenticated");
String ret;
Response response = con.httpGet(isAuthenticateUrl, null, null);
int responseCode = response.getStatusCode();
/**
* If a user is already authenticated, the return value is set to null
* and the current connection is kept open.
*/
if (responseCode == HttpURLConnection.HTTP_OK) {
ret = null;
}
/**
* If a user is not authenticated yet, return an URL at which he can
* authenticate himself via www-authenticate.
*/
else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
ret = con.buildUrl("authentication-point/authenticate");
}
/**
* If an error occurred during login, the function throws an Exception.
*/
else {
throw response.getFailure();
}
return ret;
}
private RestConnector con;
}
Unit Tests
Using the class AlmConnector I have the following unit tests so far.
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package test;
import infrastructure.Constants;
import infrastructure.Entity;
import infrastructure.Entity.Fields.Field;
import infrastructure.EntityMarshallingUtils;
import infrastructure.Response;
import infrastructure.RestConnector;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import alm.AlmConnector;
import de.consulting.bolte.gui.SmallDialog;
public class Test_DefectReader {
@Test
public void testAlmLogin() throws Exception {
AlmConnector alm = new AlmConnector();
RestConnector conn = RestConnector.getInstance();
conn.init(new HashMap<String, String>(), Constants.HOST,
Constants.DOMAIN, Constants.PROJECT);
alm.login("abolte", SmallDialog.getUserPassword("HP ALM Password"));
alm.logout();
}
@Test
public void testReadDefect() throws Exception {
AlmConnector alm = new AlmConnector();
RestConnector conn = RestConnector.getInstance();
conn.init(new HashMap<String, String>(), Constants.HOST,
Constants.DOMAIN, Constants.PROJECT);
alm.login("abolte", SmallDialog.getUserPassword("HP ALM Password"));
/*
* Needed for HP ALM version 12.0 or later.
* Per HP knowledge article KM01754222.
*/
conn.getQCSession();
/*
* Get the defect with id 300.
*/
String defectUrl = conn.buildEntityCollectionUrl("defect");
defectUrl += "/300";
Map<String, String> requestHeaders = new HashMap<String, String>();
requestHeaders.put("Accept", "application/xml");
Response res = conn.httpGet(defectUrl, null, requestHeaders);
// xml -> class instance
String postedEntityReturnedXml = res.toString();
Entity entity = EntityMarshallingUtils.marshal(Entity.class,
postedEntityReturnedXml);
/*
* Print all names available in entity defect to screen.
*/
List<Field> fields = entity.getFields().getField();
for (Field field : fields) {
System.out.println(field.getName() + " : "
+ field.getValue().size());
}
alm.logout();
alm = null;
}
}
URL Encoding
You will have to encode URLs especially when calling the web service filtering for entries using a query.
Below an example how to encode URLs in Java.
@Test
public void testUriEncoding() throws URISyntaxException,
MalformedURLException {
final String add = "http://www.huhu.com/{ähm}";
URL url = new URL(add);
URI uri = new URI(url.getProtocol(), url.getHost(), url.getPath(),
url.getQuery(), null);
System.out.println(uri.toASCIIString());
}