001/*
002 * (C) Copyright 2016 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 *     Gabriel Barata <gbarata@nuxeo.com>
018 */
019package org.nuxeo.ecm.restapi.server.jaxrs.search;
020
021import java.io.IOException;
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import javax.servlet.http.HttpServletResponse;
029import javax.ws.rs.Consumes;
030import javax.ws.rs.DELETE;
031import javax.ws.rs.GET;
032import javax.ws.rs.POST;
033import javax.ws.rs.PUT;
034import javax.ws.rs.Path;
035import javax.ws.rs.PathParam;
036import javax.ws.rs.core.Context;
037import javax.ws.rs.core.MediaType;
038import javax.ws.rs.core.MultivaluedMap;
039import javax.ws.rs.core.Response;
040import javax.ws.rs.core.UriInfo;
041
042import org.apache.commons.lang.StringUtils;
043import org.nuxeo.ecm.automation.core.util.DocumentHelper;
044import org.nuxeo.ecm.automation.core.util.Properties;
045import org.nuxeo.ecm.automation.server.jaxrs.RestOperationException;
046import org.nuxeo.ecm.core.api.DocumentModel;
047import org.nuxeo.ecm.core.api.DocumentModelList;
048import org.nuxeo.ecm.core.api.DocumentNotFoundException;
049import org.nuxeo.ecm.core.api.SortInfo;
050import org.nuxeo.ecm.core.api.model.Property;
051import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
052import org.nuxeo.ecm.platform.query.api.PageProviderService;
053import org.nuxeo.ecm.platform.query.api.QuickFilter;
054import org.nuxeo.ecm.platform.search.core.InvalidSearchParameterException;
055import org.nuxeo.ecm.platform.search.core.SavedSearch;
056import org.nuxeo.ecm.platform.search.core.SavedSearchConstants;
057import org.nuxeo.ecm.platform.search.core.SavedSearchRequest;
058import org.nuxeo.ecm.platform.search.core.SavedSearchService;
059import org.nuxeo.ecm.webengine.model.WebObject;
060import org.nuxeo.runtime.api.Framework;
061
062/**
063 * @since 8.3 Search endpoint to perform queries via rest api, with support to save and execute saved search queries.
064 */
065@WebObject(type = "search")
066public class SearchObject extends QueryExecutor {
067
068    private static final String APPLICATION_JSON_NXENTITY = "application/json+nxentity";
069
070    public static final String SAVED_SEARCHES_PAGE_PROVIDER = "SAVED_SEARCHES_ALL";
071
072    public static final String SAVED_SEARCHES_PAGE_PROVIDER_PARAMS = "SAVED_SEARCHES_ALL_PAGE_PROVIDER";
073
074    public static final String PAGE_PROVIDER_NAME_PARAM = "pageProvider";
075
076    protected SavedSearchService savedSearchService;
077
078    @Override
079    public void initialize(Object... args) {
080        initExecutor();
081        savedSearchService = Framework.getService(SavedSearchService.class);
082    }
083
084    @GET
085    @Path("lang/{queryLanguage}/execute")
086    public Object doQueryByLang(@Context UriInfo uriInfo, @PathParam("queryLanguage") String queryLanguage)
087            throws RestOperationException {
088        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
089        return queryByLang(queryLanguage, queryParams);
090    }
091
092    @GET
093    @Path("pp/{pageProviderName}/execute")
094    public Object doQueryByPageProvider(@Context UriInfo uriInfo, @PathParam("pageProviderName") String pageProviderName)
095            throws RestOperationException {
096        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
097        return queryByPageProvider(pageProviderName, queryParams);
098    }
099
100    @GET
101    @Path("pp/{pageProviderName}")
102    public Object doGetPageProviderDefinition(@PathParam("pageProviderName") String pageProviderName)
103            throws RestOperationException, IOException {
104        return buildResponse(Response.Status.OK, MediaType.APPLICATION_JSON,
105                getPageProviderDefinition(pageProviderName));
106    }
107
108    @GET
109    @Path("saved")
110    public List<SavedSearch> doGetSavedSearches(@Context UriInfo uriInfo) throws RestOperationException {
111        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
112        DocumentModelList results = queryParams.containsKey(PAGE_PROVIDER_NAME_PARAM) ? queryByPageProvider(
113                SAVED_SEARCHES_PAGE_PROVIDER_PARAMS, queryParams) : queryByPageProvider(SAVED_SEARCHES_PAGE_PROVIDER,
114                queryParams);
115        List<SavedSearch> savedSearches = new ArrayList<>(results.size());
116        for (DocumentModel doc : results) {
117            savedSearches.add(doc.getAdapter(SavedSearch.class));
118        }
119        return savedSearches;
120    }
121
122    @POST
123    @Path("saved")
124    @Consumes({ APPLICATION_JSON_NXENTITY, "application/json" })
125    public Response doSaveSearch(SavedSearchRequest request) throws RestOperationException {
126        try {
127            SavedSearch search = savedSearchService.createSavedSearch(ctx.getCoreSession(), request.getTitle(),
128                    request.getQueryParams(), null, request.getQuery(), request.getQueryLanguage(),
129                    request.getPageProviderName(), request.getPageSize(), request.getCurrentPageIndex(),
130                    request.getMaxResults(), request.getSortBy(), request.getSortOrder(), request.getContentViewData());
131            setSaveSearchParams(request.getNamedParams(), search);
132            return Response.ok(savedSearchService.saveSavedSearch(ctx.getCoreSession(), search)).build();
133        } catch (InvalidSearchParameterException | IOException e) {
134            RestOperationException err = new RestOperationException(e.getMessage());
135            err.setStatus(HttpServletResponse.SC_BAD_REQUEST);
136            throw err;
137        }
138    }
139
140    @GET
141    @Path("saved/{id}")
142    public Response doGetSavedSearch(@PathParam("id") String id) throws RestOperationException {
143        SavedSearch search;
144        try {
145            search = savedSearchService.getSavedSearch(ctx.getCoreSession(), id);
146        } catch (DocumentNotFoundException e) {
147            RestOperationException err = new RestOperationException("unknown id: " + e.getMessage());
148            err.setStatus(HttpServletResponse.SC_NOT_FOUND);
149            throw err;
150        }
151        if (search == null) {
152            return Response.status(Response.Status.NOT_FOUND).build();
153        }
154        return Response.ok(search).build();
155    }
156
157    @PUT
158    @Path("saved/{id}")
159    @Consumes({ APPLICATION_JSON_NXENTITY, "application/json" })
160    public Response doUpdateSavedSearch(SavedSearchRequest request, @PathParam("id") String id)
161            throws RestOperationException {
162        SavedSearch search;
163        try {
164            search = savedSearchService.getSavedSearch(ctx.getCoreSession(), id);
165        } catch (DocumentNotFoundException e) {
166            RestOperationException err = new RestOperationException("unknown id: " + e.getMessage());
167            err.setStatus(HttpServletResponse.SC_NOT_FOUND);
168            throw err;
169        }
170        if (search == null) {
171            return Response.status(Response.Status.NOT_FOUND).build();
172        }
173
174        search.setTitle(request.getTitle());
175        search.setQueryParams(request.getQueryParams());
176        search.setQuery(request.getQuery());
177        search.setQueryLanguage(request.getQueryLanguage());
178        search.setPageProviderName(request.getPageProviderName());
179        search.setPageSize(request.getPageSize());
180        search.setCurrentPageIndex(request.getCurrentPageIndex());
181        search.setMaxResults(request.getMaxResults());
182        search.setSortBy(request.getSortBy());
183        search.setSortOrder(request.getSortOrder());
184        search.setContentViewData(request.getContentViewData());
185        try {
186            setSaveSearchParams(request.getNamedParams(), search);
187            search = savedSearchService.saveSavedSearch(ctx.getCoreSession(), search);
188        } catch (InvalidSearchParameterException | IOException e) {
189            RestOperationException err = new RestOperationException(e.getMessage());
190            err.setStatus(HttpServletResponse.SC_BAD_REQUEST);
191            throw err;
192        }
193        return Response.ok(search).build();
194    }
195
196    @DELETE
197    @Path("saved/{id}")
198    public Response doDeleteSavedSearch(@PathParam("id") String id) throws RestOperationException {
199        try {
200            SavedSearch search = savedSearchService.getSavedSearch(ctx.getCoreSession(), id);
201            savedSearchService.deleteSavedSearch(ctx.getCoreSession(), search);
202        } catch (DocumentNotFoundException e) {
203            RestOperationException err = new RestOperationException(e.getMessage());
204            err.setStatus(HttpServletResponse.SC_NOT_FOUND);
205            throw err;
206        }
207        return Response.status(Response.Status.NO_CONTENT).build();
208    }
209
210    @GET
211    @Path("saved/{id}/execute")
212    public Object doExecuteSavedSearch(@PathParam("id") String id, @Context UriInfo uriInfo)
213            throws RestOperationException {
214        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
215        SavedSearch search = savedSearchService.getSavedSearch(ctx.getCoreSession(), id);
216        if (search == null) {
217            return Response.status(Response.Status.NOT_FOUND).build();
218        }
219        return executeSavedSearch(search, params);
220    }
221
222    protected void setSaveSearchParams(Map<String, String> params, SavedSearch search) throws IOException {
223        Map<String, String> namedParams = new HashMap<>();
224        if (params != null) {
225            for (Map.Entry<String, String> entry : params.entrySet()) {
226                String key = entry.getKey();
227                String value = entry.getValue();
228                try {
229                    Property prop = search.getDocument().getProperty(key);
230                    DocumentHelper.setProperty(search.getDocument().getCoreSession(), search.getDocument(), key, value,
231                            true);
232                } catch (PropertyNotFoundException e) {
233                    namedParams.put(key, value);
234                }
235            }
236        }
237        search.setNamedParams(namedParams);
238    }
239
240    protected DocumentModelList executeSavedSearch(SavedSearch search, MultivaluedMap<String, String> params)
241            throws RestOperationException {
242        Long pageSize = getPageSize(params);
243        Long currentPageIndex = getCurrentPageIndex(params);
244        Long maxResults = getMaxResults(params);
245        List<SortInfo> sortInfo = getSortInfo(params);
246
247        if (!StringUtils.isEmpty(search.getPageProviderName())) {
248            List<QuickFilter> quickFilters = getQuickFilters(search.getPageProviderName(), params);
249            return querySavedSearchByPageProvider(
250                    search.getPageProviderName(),
251                    pageSize != null ? pageSize : search.getPageSize(),
252                    currentPageIndex != null ? currentPageIndex : search.getCurrentPageIndex(),
253                    search.getQueryParams(),
254                    search.getNamedParams(),
255                    sortInfo != null ? sortInfo : getSortInfo(search.getSortBy(), search.getSortOrder()),
256                    quickFilters,
257                    search.getDocument().getType() != SavedSearchConstants.PARAMETERIZED_SAVED_SEARCH_TYPE_NAME ? search.getDocument()
258                            : null);
259        } else if (!StringUtils.isEmpty(search.getQuery()) && !StringUtils.isEmpty(search.getQueryLanguage())) {
260            return querySavedSearchByLang(search.getQueryLanguage(), search.getQuery(), pageSize != null ? pageSize
261                    : search.getPageSize(), currentPageIndex != null ? currentPageIndex : search.getCurrentPageIndex(),
262                    maxResults != null ? maxResults : search.getMaxResults(), search.getQueryParams(),
263                    search.getNamedParams(),
264                    sortInfo != null ? sortInfo : getSortInfo(search.getSortBy(), search.getSortOrder()));
265        } else {
266            return null;
267        }
268    }
269
270    protected DocumentModelList querySavedSearchByLang(String queryLanguage, String query, Long pageSize,
271            Long currentPageIndex, Long maxResults, String orderedParams, Map<String, String> namedParameters,
272            List<SortInfo> sortInfo) throws RestOperationException {
273        Properties namedParametersProps = getNamedParameters(namedParameters);
274        Object[] parameters = replaceParameterPattern(new Object[] { orderedParams });
275        Map<String, Serializable> props = getProperties();
276
277        DocumentModel searchDocumentModel = getSearchDocumentModel(ctx.getCoreSession(), pageProviderService, null,
278                namedParametersProps);
279
280        return queryByLang(query, pageSize, currentPageIndex, maxResults, sortInfo, parameters, props,
281                searchDocumentModel);
282    }
283
284    protected DocumentModelList querySavedSearchByPageProvider(String pageProviderName, Long pageSize,
285            Long currentPageIndex, String orderedParams, Map<String, String> namedParameters, List<SortInfo> sortInfo,
286            List<QuickFilter> quickFilters, DocumentModel searchDocumentModel) throws RestOperationException {
287        Properties namedParametersProps = getNamedParameters(namedParameters);
288        Object[] parameters = orderedParams != null ? replaceParameterPattern(new Object[] { orderedParams })
289                : new Object[0];
290        Map<String, Serializable> props = getProperties();
291
292        DocumentModel documentModel;
293        if (searchDocumentModel == null) {
294            documentModel = getSearchDocumentModel(ctx.getCoreSession(), pageProviderService, pageProviderName,
295                    namedParametersProps);
296        } else {
297            documentModel = searchDocumentModel;
298            if (namedParametersProps.size() > 0) {
299                documentModel.putContextData(PageProviderService.NAMED_PARAMETERS, namedParametersProps);
300            }
301        }
302
303        return queryByPageProvider(pageProviderName, pageSize, currentPageIndex, sortInfo, quickFilters, parameters, props,
304                documentModel);
305    }
306
307}