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.transaction.TransactionHelper;
043
044import com.fasterxml.jackson.core.JsonProcessingException;
045import com.fasterxml.jackson.databind.JsonNode;
046import com.fasterxml.jackson.databind.ObjectMapper;
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").asText());
247        assertEquals(note.getPathAsString(), node.get("path").asText());
248        assertEquals(note.getId(), node.get("uid").asText());
249        assertEquals(note.getTitle(), node.get("title").asText());
250    }
251
252    protected List<JsonNode> getLogEntries(JsonNode node) {
253        assertEquals("documents", node.get("entity-type").asText());
254        assertTrue(node.get("entries").isArray());
255        List<JsonNode> result = new ArrayList<>();
256        Iterator<JsonNode> elements = node.get("entries").elements();
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").asText());
268        assertTrue(node.get("message").isTextual());
269        return node.get("message").asText();
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}