001/*
002 *  Copyright 2020 Anyware Services
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 */
016package org.ametys.plugins.workspaces.search.module;
017
018import java.util.Collection;
019import java.util.Collections;
020import java.util.Date;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.cocoon.environment.Request;
031import org.apache.cocoon.xml.AttributesImpl;
032import org.apache.cocoon.xml.XMLUtils;
033import org.apache.commons.collections4.IterableUtils;
034import org.apache.commons.lang3.StringUtils;
035import org.apache.solr.client.solrj.util.ClientUtils;
036
037import org.ametys.cms.content.indexing.solr.SolrFieldNames;
038import org.ametys.cms.contenttype.ContentTypesHelper;
039import org.ametys.cms.search.SearchResult;
040import org.ametys.cms.search.SearchResults;
041import org.ametys.cms.search.SearchResultsIterable;
042import org.ametys.cms.search.SearchResultsIterator;
043import org.ametys.cms.search.query.AndQuery;
044import org.ametys.cms.search.query.DocumentTypeQuery;
045import org.ametys.cms.search.query.FullTextQuery;
046import org.ametys.cms.search.query.OrQuery;
047import org.ametys.cms.search.query.Query;
048import org.ametys.cms.search.query.Query.Operator;
049import org.ametys.cms.search.solr.SearcherFactory;
050import org.ametys.cms.search.solr.SearcherFactory.Searcher;
051import org.ametys.cms.transformation.xslt.ResolveURIComponent;
052import org.ametys.core.util.DateUtils;
053import org.ametys.plugins.explorer.resources.Resource;
054import org.ametys.plugins.repository.AmetysObject;
055import org.ametys.plugins.repository.AmetysObjectIterable;
056import org.ametys.plugins.repository.EmptyIterable;
057import org.ametys.plugins.workspaces.WorkspacesHelper;
058import org.ametys.plugins.workspaces.documents.DocumentWorkspaceModule;
059import org.ametys.plugins.workspaces.project.objects.Project;
060import org.ametys.plugins.workspaces.search.query.ProjectQuery;
061
062/**
063 * Generator for posts search module
064 */
065public class DocumentSearchModuleGenerator extends AbstractSolrSearchModuleGenerator
066{
067    /** Searcher Factory */
068    protected SearcherFactory _searcherFactory;
069    /** The content types helper */
070    protected ContentTypesHelper _cTypesHelper;
071    /** Workspace Helper */
072    protected WorkspacesHelper _workspaceHelper;
073    
074    @Override
075    public void service(ServiceManager smanager) throws ServiceException
076    {
077        super.service(smanager);
078        _searcherFactory = (SearcherFactory) manager.lookup(SearcherFactory.ROLE);
079        _cTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
080        _workspaceHelper = (WorkspacesHelper) manager.lookup(WorkspacesHelper.ROLE);
081    }
082    
083    @SuppressWarnings("unchecked")
084    @Override
085    protected SearchResults< ? extends AmetysObject> getSearchResults(String siteName, String lang, String textfield, Request request, int offset, int limit) throws Exception
086    {
087        Searcher searcher = _searcherFactory.create();
088        
089        List<Project> projects = getProjects(request, true);
090        
091        projects = filterProjectsForModule(projects, DocumentWorkspaceModule.DODUMENT_MODULE_ID);
092        if (projects.isEmpty())
093        {
094            // No projet availables for this module
095            return new EmptySearchResults();
096        }
097        
098        Collection<Query> queries = new HashSet<>();
099        
100        DocumentTypeQuery documentTypeQuery = new DocumentTypeQuery("projectResource");
101        queries.add(documentTypeQuery);
102        
103        if (StringUtils.isNotBlank(textfield))
104        {
105            Query fullTextQuery = new FullTextQuery("*" + textfield + "*", lang, Operator.SEARCH, false);
106
107            Query fileNameQuery = () -> SolrFieldNames.FILENAME + ":(*" + ClientUtils.escapeQueryChars(textfield) + "*)";
108            Query searchQuery = new OrQuery(fullTextQuery, fileNameQuery);
109            queries.add(searchQuery);
110        }
111        
112        Set<String> projectIds = projects.stream()
113                .map(Project::getId)
114                .collect(Collectors.toSet());
115        queries.add(new ProjectQuery(projectIds));
116        
117        Query fullQuery = new AndQuery(queries);
118        
119        return searcher
120                .withLimits(offset, limit)
121                .withQuery(fullQuery)
122                .searchWithFacets();
123    }
124
125    @Override
126    protected void saxHit(AmetysObject object, String lang) throws Exception
127    {
128        if (object instanceof Resource)
129        {
130            Resource file = (Resource) object;
131            AttributesImpl attrs = new AttributesImpl();
132            attrs.addCDATAAttribute("id", file.getId());
133            XMLUtils.startElement(contentHandler, "hit", attrs);
134            
135            XMLUtils.createElement(contentHandler, "title", file.getName());
136            XMLUtils.createElement(contentHandler, "uri", ResolveURIComponent.resolve("project-resource", file.getId(), false, true));
137            XMLUtils.createElement(contentHandler, "length", String.valueOf(file.getLength()));
138            XMLUtils.createElement(contentHandler, "mimeType", file.getMimeType());
139            XMLUtils.createElement(contentHandler, "fileType", _workspaceHelper.getFileType(file).name().toLowerCase());
140            
141            Date creationDate = file.getCreationDate();
142            XMLUtils.createElement(contentHandler, "creationDate", DateUtils.dateToString(creationDate));
143            
144            Date lastModified = file.getLastModified();
145            XMLUtils.createElement(contentHandler, "lastModified", DateUtils.dateToString(lastModified));
146            
147            Project project = getProject(object);
148            saxProject(project);
149            XMLUtils.endElement(contentHandler, "hit");
150        }
151    }
152    
153    class EmptySearchResults<A extends AmetysObject> implements SearchResults<A>
154    {
155        public SearchResultsIterable<SearchResult<A>> getResults()
156        {
157            return new EmptySearchResultsIterable<>();
158        }
159
160        public AmetysObjectIterable<A> getObjects()
161        {
162            return new EmptyIterable<>();
163        }
164
165        public Iterable<String> getObjectIds()
166        {
167            return IterableUtils.emptyIterable();
168        }
169
170        public Map<String, Map<String, Integer>> getFacetResults()
171        {
172            return Collections.EMPTY_MAP;
173        }
174
175        public long getTotalCount()
176        {
177            return 0;
178        }
179
180        public float getMaxScore()
181        {
182            return 0;
183        }
184
185        public Optional<Map<String, Object>> getDebugMap()
186        {
187            return Optional.empty();
188        }
189        
190    }
191    
192    class EmptySearchResultsIterable<A extends AmetysObject> implements SearchResultsIterable<SearchResult<A>>
193    {
194
195        public long getSize()
196        {
197            return 0;
198        }
199
200        @SuppressWarnings("unchecked")
201        public SearchResultsIterator<SearchResult<A>> iterator()
202        {
203            return new EmptySearchResultsIterator();
204        }
205    }
206    
207    class EmptySearchResultsIterator<A extends AmetysObject> implements SearchResultsIterator<SearchResult<A>>
208    {
209        EmptySearchResultsIterator()
210        {
211            // Empty
212        }
213        
214        @Override
215        public boolean hasNext()
216        {
217            return false;
218        }
219        
220        @Override
221        public SearchResult<A> next()
222        {
223            throw new UnsupportedOperationException();
224        }
225        
226        @Override
227        public void skip(long skipNum)
228        {
229            throw new UnsupportedOperationException();
230        }
231    }
232}