001/*
002 *  Copyright 2017 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.thesaurus.search;
017
018import java.util.ArrayList;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.avalon.framework.service.ServiceException;
023import org.apache.avalon.framework.service.ServiceManager;
024import org.apache.cocoon.environment.Request;
025import org.apache.commons.lang3.StringUtils;
026
027import org.ametys.cms.repository.Content;
028import org.ametys.cms.repository.SubContentExpression;
029import org.ametys.cms.search.cocoon.SearchAction;
030import org.ametys.cms.search.ui.model.SearchUIModel;
031import org.ametys.plugins.repository.AmetysObjectIterable;
032import org.ametys.plugins.repository.AmetysObjectIterator;
033import org.ametys.plugins.repository.AmetysObjectResolver;
034import org.ametys.plugins.repository.query.SortCriteria;
035import org.ametys.plugins.repository.query.expression.AndExpression;
036import org.ametys.plugins.repository.query.expression.Expression;
037import org.ametys.plugins.repository.query.expression.Expression.Operator;
038import org.ametys.plugins.repository.query.expression.NotExpression;
039import org.ametys.plugins.repository.query.expression.StringExpression;
040import org.ametys.plugins.thesaurus.MicroThesaurus;
041import org.ametys.plugins.thesaurus.ThesaurusDAO;
042
043/**
044 * Search terms by JCR query and put a result contents in the request (to be serialized in JSON).
045 */
046public class SearchTermAction extends SearchAction
047{
048    private ThesaurusDAO _thesaurusDAO;
049    private AmetysObjectResolver _resolver;
050
051    @Override
052    public void service(ServiceManager smanager) throws ServiceException
053    {
054        super.service(smanager);
055        _thesaurusDAO = (ThesaurusDAO) smanager.lookup(ThesaurusDAO.ROLE);
056        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
057    }
058    
059    @SuppressWarnings("unchecked")
060    @Override
061    protected void doSearch(Request request, SearchUIModel model, int offset, int maxResults, Map<String, Object> jsParameters, Map<String, Object> contextualParameters) throws Exception
062    {
063        List<String> contentIds = new ArrayList<>();
064        
065        // If a list of ID is present in the request, use the corresponding contents as the results.
066        if (jsParameters.get("id") != null)
067        {
068            contentIds = (List<String>) jsParameters.get("id");
069        }
070        else
071        {
072            boolean excludeSubContents = jsParameters.containsKey("excludeSubContents") ? (Boolean) jsParameters.get("excludeSubContents") : false;
073            String xPathQuery = getXPathQuery (jsParameters, excludeSubContents);
074            
075            // SAX results between begin and begin + offset
076            try (AmetysObjectIterable<Content> contents = _resolver.query(xPathQuery))
077            {
078                AmetysObjectIterator<Content> it = contents.iterator();
079                
080                // Index of search
081                it.skip(offset);
082                
083                int index = 0;
084                while (it.hasNext() && index < maxResults)
085                {
086                    Content content = it.next();
087                    contentIds.add(content.getId());
088                    index++;
089                }
090            }
091        }
092        
093        request.setAttribute(SEARCH_CONTENTS, contentIds);
094    }
095    
096    /**
097     * Get the query to execute
098     * @param jsParameters The JS parameters
099     * @param excludeSubContents true to exclude sub-contents
100     * @return the query
101     */
102    protected String getXPathQuery (Map<String, Object> jsParameters, boolean excludeSubContents)
103    {
104        List<Expression> expressions = new ArrayList<>();
105        
106        @SuppressWarnings("unchecked")
107        Map<String, Object> values = (Map<String, Object>) jsParameters.get("values");
108        if (values != null)
109        {
110            String contentType = (String) values.get("property-contentTypeOrMixin-eq");
111            if (StringUtils.isNotEmpty(contentType))
112            {
113                expressions.add(_contentTypeExtensionPoint.createHierarchicalCTExpression(contentType));
114            }
115            
116            if (excludeSubContents)
117            {
118                expressions.add(new NotExpression(new SubContentExpression()));
119            }
120            
121            String title = (String) values.get("metadata-title-search");
122            if (StringUtils.isNotEmpty(title))
123            {
124                String[] wildcardValues = title.split("\\s");
125                
126                for (String wildcardValue : wildcardValues)
127                {
128                    expressions.add(new StringExpression("title", Operator.WD, wildcardValue, false, true));
129                }
130            }
131        }
132        
133        SortCriteria sortCriteria = new SortCriteria();
134        sortCriteria.addCriterion("title", true, true);
135        
136        Expression expr = new AndExpression(expressions.toArray(new Expression[expressions.size()]));
137        String predicats = StringUtils.trimToNull(expr.build());
138        
139        String xpathQuery = "";
140        MicroThesaurus microthesaurus = getMicrothesaurus(jsParameters);
141        if (microthesaurus != null)
142        {
143            xpathQuery += "/" + microthesaurus.getPath();
144        }
145        
146        xpathQuery += "//element(*, ametys:content)" + (predicats != null ? "[" + predicats + "]" : "") + " " + sortCriteria.build();
147        
148        return xpathQuery;
149    }
150    
151    /**
152     * Get the parent microthesaurus
153     * @param jsParameters The JS parameters
154     * @return the parent microthesaurus or null
155     */
156    protected MicroThesaurus getMicrothesaurus (Map<String, Object> jsParameters)
157    {
158        String microthesaurusId = (String) jsParameters.get("microthesaurusId");
159        
160        if (StringUtils.isNotEmpty(microthesaurusId))
161        {
162            return _resolver.resolveById(microthesaurusId);
163        }
164        
165        String termId = (String) jsParameters.get("termId");
166        if (StringUtils.isNotEmpty(termId))
167        {
168            Content term = _resolver.resolveById(termId);
169            return _thesaurusDAO.getParentMicrothesaurus(term);
170        }
171        
172        return null;
173    }
174
175}