001/*
002 *  Copyright 2019 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.web.search.misc;
017
018import java.util.Arrays;
019import java.util.Collection;
020import java.util.List;
021import java.util.Map;
022import java.util.stream.Collectors;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.context.Context;
026import org.apache.avalon.framework.context.ContextException;
027import org.apache.avalon.framework.context.Contextualizable;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.cocoon.components.ContextHelper;
032import org.apache.cocoon.environment.Request;
033import org.apache.commons.lang3.StringUtils;
034
035import org.ametys.cms.search.query.AndQuery;
036import org.ametys.cms.search.query.NotQuery;
037import org.ametys.cms.search.query.Query;
038import org.ametys.cms.search.query.Query.Operator;
039import org.ametys.core.util.JSONUtils;
040import org.ametys.runtime.model.exception.BadItemTypeException;
041import org.ametys.web.repository.site.Site;
042import org.ametys.web.search.query.SiteQuery;
043
044import com.google.common.collect.ImmutableMap;
045
046/**
047 * Helper for getting a {@link SiteQuery} from a user-submitted value.
048 */
049public class SiteQueryHelper implements Component, Serviceable, Contextualizable
050{
051    /** Avalon Role */
052    public static final String ROLE = SiteQueryHelper.class.getName();
053    
054    private JSONUtils _jsonUtils;
055    private Context _context;
056    
057    @Override
058    public void contextualize(Context context) throws ContextException
059    {
060        _context = context;
061    }
062    
063    @Override
064    public void service(ServiceManager manager) throws ServiceException
065    {
066        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
067    }
068    
069    /**
070     * Gets a {@link Query} testing {@link Site} from a user-submitted value
071     * @param value a user-submitted value
072     * @param operator The operator
073     * @param contextualParameters  the search contextual parameters.
074     * @return The {@link Query}
075     */
076    @SuppressWarnings("unchecked")
077    public Query getQuery(Map<String, Object> value, Operator operator, Map<String, Object> contextualParameters)
078    {
079        // Handles a list of given site names (SITES_LIST) as well as "CURRENT_SITE", "OTHER_SITES" and "SITES" (all sites).
080        String context = (String) value.get("context");
081        
082        Request request = ContextHelper.getRequest(_context);
083        String currentSite = (String) request.getAttribute("siteName");
084        if (StringUtils.isBlank(currentSite))
085        {
086            currentSite = (String) contextualParameters.get("siteName");
087        }
088        
089        if ("CURRENT_SITE".equals(context))
090        {
091            return new SiteQuery(operator, currentSite);
092        }
093        else if ("OTHER_SITES".equals(context))
094        {
095            // other sites than current one
096            // so 'normal case' (i.e. operator = EQ) is testing <NE current_site>
097            // and 'NE case' is testing the opposite (site is not equal to other sites), <EQ current_site>
098            return new AndQuery(
099                    new SiteQuery(), 
100                    new SiteQuery(Operator.NE == operator
101                            ? Operator.EQ
102                            : Operator.NE
103                        , currentSite));
104        }
105        else if ("SITES".equals(context))
106        {
107            return Operator.NE == operator
108                    ? new NotQuery(new SiteQuery())
109                    : new SiteQuery();
110        }
111        
112        // "SITES_LIST" context: get the site list from the value map.
113        List<String> names = (List<String>) value.get("sites");
114        
115        // Standard context: site list.
116        return new SiteQuery(operator, names);
117    }
118    
119    /**
120     * Converts the given site value to have {@link Map} containing site context and value
121     * @param value the value to convert
122     * @param contextualParameters the search contextual parameters.
123     * @param criterionId The criterion id (for logging purposes only)
124     * @return the value, converted to a {@link Map}
125     * @throws BadItemTypeException if the given value can not be converted
126     */
127    @SuppressWarnings("unchecked")
128    public Map<String, Object> convertQueryValue(Object value, Map<String, Object> contextualParameters, String criterionId) throws BadItemTypeException
129    {
130        if (value == null)
131        {
132            return null;
133        }
134        else if (value instanceof Map)
135        {
136            return (Map<String, Object>) value;
137        }
138        else if (value instanceof Collection)
139        {
140            String strValue = ((Collection<?>) value).stream()
141                    .filter(String.class::isInstance)
142                    .map(String.class::cast)
143                    .collect(Collectors.joining(","));
144            return convertQueryValue(strValue, contextualParameters, criterionId);
145        }
146        else if (value instanceof String)
147        {
148            String strValue = (String) value;
149            if (strValue.startsWith("{"))
150            {
151                // Usually when coming from BO site widget
152                return _jsonUtils.convertJsonToMap(strValue);
153            }
154            else
155            {
156                // Consider it as the list of sites itself
157                // Usually when coming from FO
158                return ImmutableMap.of(
159                        "context", "SITES_LIST", 
160                        "sites", Arrays.asList(strValue.split(","))
161                );
162            }
163        }
164        else
165        {
166            throw new BadItemTypeException("The value " + value + " for criterion " + criterionId + " is not a valid Site value.");
167        }
168    }
169}
170