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 static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
022
023import java.io.IOException;
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import javax.ws.rs.Consumes;
031import javax.ws.rs.DELETE;
032import javax.ws.rs.GET;
033import javax.ws.rs.POST;
034import javax.ws.rs.PUT;
035import javax.ws.rs.Path;
036import javax.ws.rs.PathParam;
037import javax.ws.rs.core.Context;
038import javax.ws.rs.core.MediaType;
039import javax.ws.rs.core.MultivaluedMap;
040import javax.ws.rs.core.Response;
041import javax.ws.rs.core.UriInfo;
042
043import org.apache.commons.lang.StringUtils;
044import org.nuxeo.ecm.automation.core.util.DocumentHelper;
045import org.nuxeo.ecm.automation.core.util.Properties;
046import org.nuxeo.ecm.core.api.DocumentModel;
047import org.nuxeo.ecm.core.api.DocumentModelList;
048import org.nuxeo.ecm.core.api.NuxeoException;
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        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,
094            @PathParam("pageProviderName") String pageProviderName) {
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 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) {
110        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
111        DocumentModelList results = queryParams.containsKey(PAGE_PROVIDER_NAME_PARAM)
112                ? queryByPageProvider(SAVED_SEARCHES_PAGE_PROVIDER_PARAMS, queryParams)
113                : queryByPageProvider(SAVED_SEARCHES_PAGE_PROVIDER, 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) {
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            throw new NuxeoException(e.getMessage(), SC_BAD_REQUEST);
134        }
135    }
136
137    @GET
138    @Path("saved/{id}")
139    public Response doGetSavedSearch(@PathParam("id") String id) {
140        SavedSearch search = savedSearchService.getSavedSearch(ctx.getCoreSession(), id);
141        if (search == null) {
142            return Response.status(Response.Status.NOT_FOUND).build();
143        }
144        return Response.ok(search).build();
145    }
146
147    @PUT
148    @Path("saved/{id}")
149    @Consumes({ APPLICATION_JSON_NXENTITY, "application/json" })
150    public Response doUpdateSavedSearch(SavedSearchRequest request, @PathParam("id") String id) {
151        SavedSearch search = savedSearchService.getSavedSearch(ctx.getCoreSession(), id);
152        if (search == null) {
153            return Response.status(Response.Status.NOT_FOUND).build();
154        }
155
156        search.setTitle(request.getTitle());
157        search.setQueryParams(request.getQueryParams());
158        search.setQuery(request.getQuery());
159        search.setQueryLanguage(request.getQueryLanguage());
160        search.setPageProviderName(request.getPageProviderName());
161        search.setPageSize(request.getPageSize());
162        search.setCurrentPageIndex(request.getCurrentPageIndex());
163        search.setMaxResults(request.getMaxResults());
164        search.setSortBy(request.getSortBy());
165        search.setSortOrder(request.getSortOrder());
166        search.setContentViewData(request.getContentViewData());
167        try {
168            setSaveSearchParams(request.getNamedParams(), search);
169            search = savedSearchService.saveSavedSearch(ctx.getCoreSession(), search);
170        } catch (InvalidSearchParameterException | IOException e) {
171            throw new NuxeoException(e.getMessage(), SC_BAD_REQUEST);
172        }
173        return Response.ok(search).build();
174    }
175
176    @DELETE
177    @Path("saved/{id}")
178    public Response doDeleteSavedSearch(@PathParam("id") String id) {
179        SavedSearch search = savedSearchService.getSavedSearch(ctx.getCoreSession(), id);
180        savedSearchService.deleteSavedSearch(ctx.getCoreSession(), search);
181        return Response.status(Response.Status.NO_CONTENT).build();
182    }
183
184    @GET
185    @Path("saved/{id}/execute")
186    public Object doExecuteSavedSearch(@PathParam("id") String id, @Context UriInfo uriInfo) {
187        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
188        SavedSearch search = savedSearchService.getSavedSearch(ctx.getCoreSession(), id);
189        if (search == null) {
190            return Response.status(Response.Status.NOT_FOUND).build();
191        }
192        return executeSavedSearch(search, params);
193    }
194
195    protected void setSaveSearchParams(Map<String, String> params, SavedSearch search) throws IOException {
196        Map<String, String> namedParams = new HashMap<>();
197        if (params != null) {
198            for (Map.Entry<String, String> entry : params.entrySet()) {
199                String key = entry.getKey();
200                String value = entry.getValue();
201                try {
202                    Property prop = search.getDocument().getProperty(key);
203                    DocumentHelper.setProperty(search.getDocument().getCoreSession(), search.getDocument(), key, value,
204                            true);
205                } catch (PropertyNotFoundException e) {
206                    namedParams.put(key, value);
207                }
208            }
209        }
210        search.setNamedParams(namedParams);
211    }
212
213    protected DocumentModelList executeSavedSearch(SavedSearch search, MultivaluedMap<String, String> params) {
214        Long pageSize = getPageSize(params);
215        Long currentPageIndex = getCurrentPageIndex(params);
216        Long currentPageOffset = getCurrentPageOffset(params);
217        Long maxResults = getMaxResults(params);
218        List<SortInfo> sortInfo = getSortInfo(params);
219
220        if (!StringUtils.isEmpty(search.getPageProviderName())) {
221            List<QuickFilter> quickFilters = getQuickFilters(search.getPageProviderName(), params);
222            return querySavedSearchByPageProvider(search.getPageProviderName(),
223                    pageSize != null ? pageSize : search.getPageSize(),
224                    currentPageIndex != null ? currentPageIndex : search.getCurrentPageIndex(),
225                    currentPageOffset != null ? currentPageOffset : search.getCurrentPageOffset(),
226                    search.getQueryParams(), search.getNamedParams(),
227                    sortInfo != null ? sortInfo : getSortInfo(search.getSortBy(), search.getSortOrder()), quickFilters,
228                    search.getDocument().getType() != SavedSearchConstants.PARAMETERIZED_SAVED_SEARCH_TYPE_NAME
229                            ? search.getDocument()
230                            : null);
231        } else if (!StringUtils.isEmpty(search.getQuery()) && !StringUtils.isEmpty(search.getQueryLanguage())) {
232            return querySavedSearchByLang(search.getQueryLanguage(), search.getQuery(),
233                    pageSize != null ? pageSize : search.getPageSize(),
234                    currentPageIndex != null ? currentPageIndex : search.getCurrentPageIndex(),
235                    currentPageOffset != null ? currentPageOffset : search.getCurrentPageOffset(),
236                    maxResults != null ? maxResults : search.getMaxResults(), search.getQueryParams(),
237                    search.getNamedParams(),
238                    sortInfo != null ? sortInfo : getSortInfo(search.getSortBy(), search.getSortOrder()));
239        } else {
240            return null;
241        }
242    }
243
244    protected DocumentModelList querySavedSearchByLang(String queryLanguage, String query, Long pageSize,
245            Long currentPageIndex, Long currentPageOffset, Long maxResults, String orderedParams,
246            Map<String, String> namedParameters, List<SortInfo> sortInfo) {
247        Properties namedParametersProps = getNamedParameters(namedParameters);
248        Object[] parameters = replaceParameterPattern(new Object[] { orderedParams });
249        Map<String, Serializable> props = getProperties();
250
251        DocumentModel searchDocumentModel = getSearchDocumentModel(ctx.getCoreSession(), pageProviderService, null,
252                namedParametersProps);
253
254        return queryByLang(query, pageSize, currentPageIndex, currentPageOffset, maxResults, sortInfo, parameters,
255                props, searchDocumentModel);
256    }
257
258    protected DocumentModelList querySavedSearchByPageProvider(String pageProviderName, Long pageSize,
259            Long currentPageIndex, Long currentPageOffset, String orderedParams, Map<String, String> namedParameters,
260            List<SortInfo> sortInfo, List<QuickFilter> quickFilters, DocumentModel searchDocumentModel) {
261        Properties namedParametersProps = getNamedParameters(namedParameters);
262        Object[] parameters = orderedParams != null ? replaceParameterPattern(new Object[] { orderedParams })
263                : new Object[0];
264        Map<String, Serializable> props = getProperties();
265
266        DocumentModel documentModel;
267        if (searchDocumentModel == null) {
268            documentModel = getSearchDocumentModel(ctx.getCoreSession(), pageProviderService, pageProviderName,
269                    namedParametersProps);
270        } else {
271            documentModel = searchDocumentModel;
272            if (namedParametersProps.size() > 0) {
273                documentModel.putContextData(PageProviderService.NAMED_PARAMETERS, namedParametersProps);
274            }
275        }
276
277        return queryByPageProvider(pageProviderName, pageSize, currentPageIndex, currentPageOffset, sortInfo,
278                quickFilters, parameters, props, documentModel);
279    }
280
281}