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.junit.After; 037import org.junit.Before; 038import org.nuxeo.ecm.core.api.CoreSession; 039import org.nuxeo.ecm.core.api.DocumentModel; 040import org.nuxeo.jaxrs.test.CloseableClientResponse; 041import org.nuxeo.jaxrs.test.JerseyClientHelper; 042import org.nuxeo.runtime.test.runner.ServletContainerFeature; 043import org.nuxeo.runtime.transaction.TransactionHelper; 044 045import com.fasterxml.jackson.core.JsonProcessingException; 046import com.fasterxml.jackson.databind.JsonNode; 047import com.fasterxml.jackson.databind.ObjectMapper; 048import com.sun.jersey.api.client.Client; 049import com.sun.jersey.api.client.ClientResponse; 050import com.sun.jersey.api.client.WebResource; 051import com.sun.jersey.api.client.WebResource.Builder; 052import com.sun.jersey.multipart.MultiPart; 053import com.sun.jersey.multipart.MultiPartMediaTypes; 054 055/** 056 * @since 5.7.2 057 */ 058public class BaseTest { 059 060 @Inject 061 protected ServletContainerFeature servletContainerFeature; 062 063 protected static enum RequestType { 064 GET, POST, DELETE, PUT, POSTREQUEST 065 } 066 067 protected ObjectMapper mapper; 068 069 protected Client client; 070 071 protected WebResource service; 072 073 @Before 074 public void doBefore() throws Exception { 075 service = getServiceFor("Administrator", "Administrator"); 076 mapper = new ObjectMapper(); 077 } 078 079 @After 080 public void doAfter() throws Exception { 081 client.destroy(); 082 } 083 084 protected String getBaseURL() { 085 int port = servletContainerFeature.getPort(); 086 return "http://localhost:" + port; 087 } 088 089 protected String getRestApiUrl () { 090 return getBaseURL() + "/api/v1/"; 091 } 092 093 /** 094 * Returns a {@link WebResource} to perform REST API calls with the given credentials. 095 * <p> 096 * Since 9.3, uses the Apache HTTP client, more reliable and much more configurable than the one from the JDK. 097 * 098 * @since 5.7.3 099 */ 100 protected WebResource getServiceFor(String username, String password) { 101 return getServiceFor(getRestApiUrl(), username, password); 102 } 103 104 /** 105 * Returns a {@link WebResource} to perform calls on the given resource with the given credentials. 106 * <p> 107 * Uses the Apache HTTP client, more reliable and much more configurable than the one from the JDK. 108 * 109 * @since 9.3 110 */ 111 protected WebResource getServiceFor(String resource, String username, String password) { 112 if (client != null) { 113 client.destroy(); 114 } 115 client = JerseyClientHelper.clientBuilder().setCredentials(username, password).build(); 116 return client.resource(resource); 117 } 118 119 @Inject 120 public CoreSession session; 121 122 protected CloseableClientResponse getResponse(RequestType requestType, String path) { 123 return getResponse(requestType, path, null, null, null, null); 124 } 125 126 protected CloseableClientResponse getResponse(RequestType requestType, String path, Map<String, String> headers) { 127 return getResponse(requestType, path, null, null, null, headers); 128 } 129 130 protected CloseableClientResponse getResponse(RequestType requestType, String path, MultiPart mp) { 131 return getResponse(requestType, path, null, null, mp, null); 132 } 133 134 protected CloseableClientResponse getResponse(RequestType requestType, String path, MultiPart mp, 135 Map<String, String> headers) { 136 return getResponse(requestType, path, null, null, mp, headers); 137 } 138 139 protected CloseableClientResponse getResponse(RequestType requestType, String path, 140 MultivaluedMap<String, String> queryParams) { 141 return getResponse(requestType, path, null, queryParams, null, null); 142 } 143 144 protected CloseableClientResponse getResponse(RequestType requestType, String path, String data) { 145 return getResponse(requestType, path, data, null, null, null); 146 } 147 148 protected CloseableClientResponse getResponse(RequestType requestType, String path, String data, 149 Map<String, String> headers) { 150 return getResponse(requestType, path, data, null, null, headers); 151 } 152 153 protected CloseableClientResponse getResponse(RequestType requestType, String path, String data, 154 MultivaluedMap<String, String> queryParams, MultiPart mp, Map<String, String> headers) { 155 156 WebResource wr = service.path(path); 157 158 if (queryParams != null && !queryParams.isEmpty()) { 159 wr = wr.queryParams(queryParams); 160 } 161 Builder builder; 162 builder = wr.accept(MediaType.APPLICATION_JSON).header("X-NXDocumentProperties", "dublincore"); 163 164 // Adding some headers if needed 165 if (headers != null && !headers.isEmpty()) { 166 for (String headerKey : headers.keySet()) { 167 builder.header(headerKey, headers.get(headerKey)); 168 } 169 } 170 ClientResponse response = null; 171 switch (requestType) { 172 case GET: 173 response = builder.get(ClientResponse.class); 174 break; 175 case POST: 176 case POSTREQUEST: 177 if (mp != null) { 178 response = builder.type(MultiPartMediaTypes.createFormData()).post(ClientResponse.class, mp); 179 } else if (data != null) { 180 setJSONContentTypeIfAbsent(builder, headers); 181 response = builder.post(ClientResponse.class, data); 182 } else { 183 response = builder.post(ClientResponse.class); 184 } 185 break; 186 case PUT: 187 if (mp != null) { 188 response = builder.type(MultiPartMediaTypes.createFormData()).put(ClientResponse.class, mp); 189 } else if (data != null) { 190 setJSONContentTypeIfAbsent(builder, headers); 191 response = builder.put(ClientResponse.class, data); 192 } else { 193 response = builder.put(ClientResponse.class); 194 } 195 break; 196 case DELETE: 197 response = builder.delete(ClientResponse.class, data); 198 break; 199 default: 200 throw new RuntimeException(); 201 } 202 203 // Make the ClientResponse AutoCloseable by wrapping it in a CloseableClientResponse. 204 // This is to strongly encourage the caller to use a try-with-resources block to make sure the response is 205 // closed and avoid leaking connections. 206 return CloseableClientResponse.of(response); 207 } 208 209 /** @since 9.3 */ 210 protected void setJSONContentTypeIfAbsent(Builder builder, Map<String, String> headers) { 211 if (headers == null || !(headers.containsKey("Content-Type"))) { 212 builder.type(MediaType.APPLICATION_JSON); 213 } 214 } 215 216 protected JsonNode getResponseAsJson(RequestType responseType, String url) 217 throws IOException, JsonProcessingException { 218 return getResponseAsJson(responseType, url, null); 219 } 220 221 /** 222 * @param get 223 * @param string 224 * @param queryParamsForPage 225 * @return 226 * @throws IOException 227 * @throws JsonProcessingException 228 * @since 5.8 229 */ 230 protected JsonNode getResponseAsJson(RequestType responseType, String url, 231 MultivaluedMap<String, String> queryParams) throws JsonProcessingException, IOException { 232 try (CloseableClientResponse response = getResponse(responseType, url, queryParams)) { 233 assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); 234 return mapper.readTree(response.getEntityInputStream()); 235 } 236 } 237 238 /** 239 * Fetch session invalidations. 240 * 241 * @since 5.9.3 242 */ 243 protected void fetchInvalidations() { 244 session.save(); 245 if (TransactionHelper.isTransactionActiveOrMarkedRollback()) { 246 TransactionHelper.commitOrRollbackTransaction(); 247 TransactionHelper.startTransaction(); 248 } 249 } 250 251 protected void assertNodeEqualsDoc(JsonNode node, DocumentModel note) throws Exception { 252 assertEquals("document", node.get("entity-type").asText()); 253 assertEquals(note.getPathAsString(), node.get("path").asText()); 254 assertEquals(note.getId(), node.get("uid").asText()); 255 assertEquals(note.getTitle(), node.get("title").asText()); 256 } 257 258 protected List<JsonNode> getLogEntries(JsonNode node) { 259 assertEquals("documents", node.get("entity-type").asText()); 260 assertTrue(node.get("entries").isArray()); 261 List<JsonNode> result = new ArrayList<>(); 262 Iterator<JsonNode> elements = node.get("entries").elements(); 263 while (elements.hasNext()) { 264 result.add(elements.next()); 265 } 266 return result; 267 } 268 269 /** 270 * @since 7.1 271 */ 272 protected String getErrorMessage(JsonNode node) { 273 assertEquals("exception", node.get("entity-type").asText()); 274 assertTrue(node.get("message").isTextual()); 275 return node.get("message").asText(); 276 } 277 278 protected void assertEntityEqualsDoc(InputStream in, DocumentModel doc) throws Exception { 279 280 JsonNode node = mapper.readTree(in); 281 assertNodeEqualsDoc(node, doc); 282 283 } 284}