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