001/* 002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * dmetzler 018 */ 019package org.nuxeo.ecm.restapi.test; 020 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertTrue; 023 024import java.io.IOException; 025import java.io.InputStream; 026import java.util.ArrayList; 027import java.util.Iterator; 028import java.util.List; 029import java.util.Map; 030 031import javax.inject.Inject; 032import javax.ws.rs.core.MediaType; 033import javax.ws.rs.core.MultivaluedMap; 034import javax.ws.rs.core.Response; 035 036import org.codehaus.jackson.JsonNode; 037import org.codehaus.jackson.JsonProcessingException; 038import org.codehaus.jackson.map.ObjectMapper; 039import org.junit.After; 040import org.junit.Before; 041import org.nuxeo.ecm.core.api.CoreSession; 042import org.nuxeo.ecm.core.api.DocumentModel; 043import org.nuxeo.jaxrs.test.CloseableClientResponse; 044import org.nuxeo.jaxrs.test.JerseyClientHelper; 045import org.nuxeo.runtime.transaction.TransactionHelper; 046 047import com.sun.jersey.api.client.Client; 048import com.sun.jersey.api.client.ClientResponse; 049import com.sun.jersey.api.client.WebResource; 050import com.sun.jersey.api.client.WebResource.Builder; 051import com.sun.jersey.multipart.MultiPart; 052import com.sun.jersey.multipart.MultiPartMediaTypes; 053 054/** 055 * @since 5.7.2 056 */ 057public class BaseTest { 058 059 protected static final String REST_API_URL = "http://localhost:18090/api/v1/"; 060 061 protected static enum RequestType { 062 GET, POST, DELETE, PUT, POSTREQUEST, GETES 063 } 064 065 protected ObjectMapper mapper; 066 067 protected Client client; 068 069 protected WebResource service; 070 071 @Before 072 public void doBefore() throws Exception { 073 service = getServiceFor("Administrator", "Administrator"); 074 mapper = new ObjectMapper(); 075 } 076 077 @After 078 public void doAfter() throws Exception { 079 client.destroy(); 080 } 081 082 /** 083 * Returns a {@link WebResource} to perform REST API calls with the given credentials. 084 * <p> 085 * Since 9.3, uses the Apache HTTP client, more reliable and much more configurable than the one from the JDK. 086 * 087 * @since 5.7.3 088 */ 089 protected WebResource getServiceFor(String username, String password) { 090 return getServiceFor(REST_API_URL, username, password); 091 } 092 093 /** 094 * Returns a {@link WebResource} to perform calls on the given resource with the given credentials. 095 * <p> 096 * Uses the Apache HTTP client, more reliable and much more configurable than the one from the JDK. 097 * 098 * @since 9.3 099 */ 100 protected WebResource getServiceFor(String resource, String username, String password) { 101 if (client != null) { 102 client.destroy(); 103 } 104 client = JerseyClientHelper.clientBuilder().setCredentials(username, password).build(); 105 return client.resource(resource); 106 } 107 108 @Inject 109 public CoreSession session; 110 111 protected CloseableClientResponse getResponse(RequestType requestType, String path) { 112 return getResponse(requestType, path, null, null, null, null); 113 } 114 115 protected CloseableClientResponse getResponse(RequestType requestType, String path, Map<String, String> headers) { 116 return getResponse(requestType, path, null, null, null, headers); 117 } 118 119 protected CloseableClientResponse getResponse(RequestType requestType, String path, MultiPart mp) { 120 return getResponse(requestType, path, null, null, mp, null); 121 } 122 123 protected CloseableClientResponse getResponse(RequestType requestType, String path, MultiPart mp, 124 Map<String, String> headers) { 125 return getResponse(requestType, path, null, null, mp, headers); 126 } 127 128 protected CloseableClientResponse getResponse(RequestType requestType, String path, 129 MultivaluedMap<String, String> queryParams) { 130 return getResponse(requestType, path, null, queryParams, null, null); 131 } 132 133 protected CloseableClientResponse getResponse(RequestType requestType, String path, String data) { 134 return getResponse(requestType, path, data, null, null, null); 135 } 136 137 protected CloseableClientResponse getResponse(RequestType requestType, String path, String data, 138 Map<String, String> headers) { 139 return getResponse(requestType, path, data, null, null, headers); 140 } 141 142 protected CloseableClientResponse getResponse(RequestType requestType, String path, String data, 143 MultivaluedMap<String, String> queryParams, MultiPart mp, Map<String, String> headers) { 144 145 WebResource wr = service.path(path); 146 147 if (queryParams != null && !queryParams.isEmpty()) { 148 wr = wr.queryParams(queryParams); 149 } 150 Builder builder; 151 if (requestType == RequestType.GETES) { 152 builder = wr.accept("application/json+esentity"); 153 } else { 154 builder = wr.accept(MediaType.APPLICATION_JSON).header("X-NXDocumentProperties", "dublincore"); 155 } 156 157 // Adding some headers if needed 158 if (headers != null && !headers.isEmpty()) { 159 for (String headerKey : headers.keySet()) { 160 builder.header(headerKey, headers.get(headerKey)); 161 } 162 } 163 ClientResponse response = null; 164 switch (requestType) { 165 case GET: 166 case GETES: 167 response = builder.get(ClientResponse.class); 168 break; 169 case POST: 170 case POSTREQUEST: 171 if (mp != null) { 172 response = builder.type(MultiPartMediaTypes.createFormData()).post(ClientResponse.class, mp); 173 } else if (data != null) { 174 setJSONContentTypeIfAbsent(builder, headers); 175 response = builder.post(ClientResponse.class, data); 176 } else { 177 response = builder.post(ClientResponse.class); 178 } 179 break; 180 case PUT: 181 if (mp != null) { 182 response = builder.type(MultiPartMediaTypes.createFormData()).put(ClientResponse.class, mp); 183 } else if (data != null) { 184 setJSONContentTypeIfAbsent(builder, headers); 185 response = builder.put(ClientResponse.class, data); 186 } else { 187 response = builder.put(ClientResponse.class); 188 } 189 break; 190 case DELETE: 191 response = builder.delete(ClientResponse.class, data); 192 break; 193 default: 194 throw new RuntimeException(); 195 } 196 197 // Make the ClientResponse AutoCloseable by wrapping it in a CloseableClientResponse. 198 // This is to strongly encourage the caller to use a try-with-resources block to make sure the response is 199 // closed and avoid leaking connections. 200 return CloseableClientResponse.of(response); 201 } 202 203 /** @since 9.3 */ 204 protected void setJSONContentTypeIfAbsent(Builder builder, Map<String, String> headers) { 205 if (headers == null || !(headers.containsKey("Content-Type"))) { 206 builder.type(MediaType.APPLICATION_JSON); 207 } 208 } 209 210 protected JsonNode getResponseAsJson(RequestType responseType, String url) 211 throws IOException, JsonProcessingException { 212 return getResponseAsJson(responseType, url, null); 213 } 214 215 /** 216 * @param get 217 * @param string 218 * @param queryParamsForPage 219 * @return 220 * @throws IOException 221 * @throws JsonProcessingException 222 * @since 5.8 223 */ 224 protected JsonNode getResponseAsJson(RequestType responseType, String url, 225 MultivaluedMap<String, String> queryParams) throws JsonProcessingException, IOException { 226 try (CloseableClientResponse response = getResponse(responseType, url, queryParams)) { 227 assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); 228 return mapper.readTree(response.getEntityInputStream()); 229 } 230 } 231 232 /** 233 * Fetch session invalidations. 234 * 235 * @since 5.9.3 236 */ 237 protected void fetchInvalidations() { 238 session.save(); 239 if (TransactionHelper.isTransactionActiveOrMarkedRollback()) { 240 TransactionHelper.commitOrRollbackTransaction(); 241 TransactionHelper.startTransaction(); 242 } 243 } 244 245 protected void assertNodeEqualsDoc(JsonNode node, DocumentModel note) throws Exception { 246 assertEquals("document", node.get("entity-type").getValueAsText()); 247 assertEquals(note.getPathAsString(), node.get("path").getValueAsText()); 248 assertEquals(note.getId(), node.get("uid").getValueAsText()); 249 assertEquals(note.getTitle(), node.get("title").getValueAsText()); 250 } 251 252 protected List<JsonNode> getLogEntries(JsonNode node) { 253 assertEquals("documents", node.get("entity-type").getValueAsText()); 254 assertTrue(node.get("entries").isArray()); 255 List<JsonNode> result = new ArrayList<>(); 256 Iterator<JsonNode> elements = node.get("entries").getElements(); 257 while (elements.hasNext()) { 258 result.add(elements.next()); 259 } 260 return result; 261 } 262 263 /** 264 * @since 7.1 265 */ 266 protected String getErrorMessage(JsonNode node) { 267 assertEquals("exception", node.get("entity-type").getValueAsText()); 268 assertTrue(node.get("message").isTextual()); 269 return node.get("message").getValueAsText(); 270 } 271 272 protected void assertEntityEqualsDoc(InputStream in, DocumentModel doc) throws Exception { 273 274 JsonNode node = mapper.readTree(in); 275 assertNodeEqualsDoc(node, doc); 276 277 } 278}