001/*
002 *  Copyright 2018 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.repository.page;
017
018import java.io.IOException;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.cocoon.ProcessingException;
033import org.apache.cocoon.components.ContextHelper;
034import org.apache.cocoon.environment.Request;
035import org.apache.commons.lang.StringUtils;
036import org.apache.commons.lang3.tuple.ImmutablePair;
037import org.apache.commons.lang3.tuple.Pair;
038
039import org.ametys.core.observation.Event;
040import org.ametys.core.observation.ObservationManager;
041import org.ametys.core.ui.Callable;
042import org.ametys.core.user.CurrentUserProvider;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.UnknownAmetysObjectException;
045import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
046import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
047import org.ametys.runtime.i18n.I18nizableText;
048import org.ametys.runtime.model.DefinitionContext;
049import org.ametys.runtime.model.ModelItem;
050import org.ametys.runtime.model.View;
051import org.ametys.runtime.model.ViewItem;
052import org.ametys.runtime.model.ViewItemContainer;
053import org.ametys.runtime.plugin.component.AbstractLogEnabled;
054import org.ametys.web.ObservationConstants;
055import org.ametys.web.WebConstants;
056import org.ametys.web.parameters.ParametersManager;
057import org.ametys.web.parameters.view.ViewParametersDAO;
058import org.ametys.web.parameters.view.ViewParametersManager;
059import org.ametys.web.parameters.view.ViewParametersModel;
060import org.ametys.web.repository.page.ZoneItem.ZoneType;
061import org.ametys.web.service.Service;
062import org.ametys.web.service.ServiceExtensionPoint;
063
064/**
065 * Class containing callables to retrieve and configure service parameters
066 */
067public class ZoneItemManager extends AbstractLogEnabled implements Component, Serviceable, Contextualizable
068{
069    /** Avalon Role */
070    public static final String ROLE = ZoneItemManager.class.getName();
071    
072    /** Constant for untouched binary metadata. */
073    protected static final String __SERVICE_PARAM_UNTOUCHED_BINARY = "untouched";
074    
075    private ServiceExtensionPoint _serviceExtensionPoint;
076    private AmetysObjectResolver _resolver;
077    private ObservationManager _observationManager;
078    private CurrentUserProvider _currentUserProvider;
079    private ParametersManager _parametersManager;
080    private ViewParametersManager _viewParametersManager;
081    private Context _context;
082
083    public void service(ServiceManager manager) throws ServiceException
084    {
085        _serviceExtensionPoint = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE);
086        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
087        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
088        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
089        _parametersManager = (ParametersManager) manager.lookup(ParametersManager.ROLE);
090        _viewParametersManager = (ViewParametersManager) manager.lookup(ViewParametersManager.ROLE);
091    }
092    
093    public void contextualize(Context context) throws ContextException
094    {
095        _context = context;
096    }
097    
098    /**
099     * Retrieves the parameter definitions of the given service
100     * @param serviceId Identifier of the service
101     * @param pageId the page id
102     * @param zoneItemId the zone item id
103     * @param zoneName the zone name
104     * @return the parameter definitions
105     * @throws ProcessingException if an error occurs
106     */
107    @Callable
108    public Map<String, Object> getServiceParameterDefinitions(String serviceId, String pageId, String zoneItemId, String zoneName) throws ProcessingException
109    {
110        _setRequestAttribute(serviceId, pageId, zoneItemId, zoneName);
111        
112        Map<String, Object> response = new HashMap<>();
113        
114        Service service = _serviceExtensionPoint.getExtension(serviceId);
115        
116        response.put("id", serviceId);
117        response.put("url", service.getURL());
118        response.put("label", service.getLabel());
119        response.put("height", service.getCreationBoxHeight());
120        response.put("width", service.getCreationBoxWidth());
121        
122        View serviceView = service.getView();
123        
124        SitemapElement sitemapElement = _resolver.resolveById(pageId);
125        
126        Map<String, ViewParametersModel> serviceViewParametersModels = _viewParametersManager.getServiceViewParametersModels(sitemapElement.getSite().getSkinId(), serviceId);
127        
128        Pair<ViewItemContainer, Integer> containerAndIndex = _getXSLTViewItemContainerAndIndex(serviceView);
129        if (containerAndIndex != null)
130        {
131            for (String viewName : serviceViewParametersModels.keySet())
132            {
133                ViewParametersModel viewParameters = serviceViewParametersModels.get(viewName);
134                Collection< ? extends ModelItem> modelItems = viewParameters.getModelItems();
135                
136                String prefix = ViewParametersDAO.PREFIX_SERVICE + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR + _viewParametersManager.normalizeViewName(viewName) + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR;
137                Optional<Integer> index = Optional.of(containerAndIndex.getRight() + 1); //add 1 to the XSLT parameter index to include the view parameters after the XSLT one
138                List<ModelItem> includeModelItems = _viewParametersManager.includeModelItems(modelItems, prefix, containerAndIndex.getLeft(), index); //add 1 to the XSL
139                _viewParametersManager.setDisableConditions(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME, viewName, includeModelItems);
140            }
141        }
142        
143        response.put("parameters", serviceView.toJSON(DefinitionContext.newInstance().withEdition(true)));
144        
145        return response;
146    }
147    
148    private Pair<ViewItemContainer, Integer> _getXSLTViewItemContainerAndIndex(ViewItemContainer viewItemContainer)
149    {
150        List<ViewItem> viewItems = viewItemContainer.getViewItems();
151        for (int i = 0; i < viewItems.size(); i++)
152        {
153            ViewItem viewItem = viewItems.get(i);
154
155            if (ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME.equals(viewItem.getName()))
156            {
157                return ImmutablePair.of(viewItemContainer, i);
158            }
159            else if (viewItem instanceof ViewItemContainer)
160            {
161                Pair<ViewItemContainer, Integer> xsltContainerAndIndex = _getXSLTViewItemContainerAndIndex((ViewItemContainer) viewItem);
162                if (xsltContainerAndIndex != null)
163                {
164                    return xsltContainerAndIndex;
165                }
166            }
167        }
168        
169        return null;
170    }
171    
172    private void _setRequestAttribute(String serviceId, String pageId, String zoneItemId, String zoneName)
173    {
174        Request request = ContextHelper.getRequest(_context);
175        
176        request.setAttribute(WebConstants.REQUEST_ATTR_SERVICE_ID, serviceId);
177        request.setAttribute(WebConstants.REQUEST_ATTR_PAGE_ID, pageId);
178        request.setAttribute(WebConstants.REQUEST_ATTR_ZONEITEM_ID, zoneItemId);
179        request.setAttribute(WebConstants.REQUEST_ATTR_ZONE_NAME, zoneName);
180    }
181    
182    /**
183     * Get the service parameter values
184     * @param zoneItemId the zone item id
185     * @param serviceId the service Id
186     * @return the values
187     */
188    @Callable
189    public Map<String, Object> getServiceParameterValues(String zoneItemId, String serviceId)
190    {
191        Service service = _serviceExtensionPoint.getExtension(serviceId);
192        ZoneItem zoneItem = _resolver.resolveById(zoneItemId);
193        
194        Map<String, Object> response = new HashMap<>();
195        Collection<ModelItem> serviceParameterDefinitions = service.getParameters().values();
196        ModelAwareDataHolder dataHolder = zoneItem.getServiceParameters();
197
198        Map<String, Object> values = _parametersManager.getParametersValues(serviceParameterDefinitions, dataHolder, "");
199        values.putAll(_getServiceViewParametersValues(zoneItem));
200        
201        response.put("values", values);
202        
203        List<Map<String, Object>> repeaters = _parametersManager.getRepeatersValues(serviceParameterDefinitions, dataHolder, "");
204        response.put("repeaters", repeaters);
205        
206        return response;
207    }
208    
209    /**
210     * Get the service view parameters values
211     * @param zoneItem the zone item
212     * @return the values
213     */
214    protected Map<String, Object> _getServiceViewParametersValues(ZoneItem zoneItem)
215    {
216        Map<String, Object> values = new HashMap<>();
217        
218        String skinId = zoneItem.getZone()
219                .getSitemapElement()
220                .getSite()
221                .getSkinId();
222
223        Map<String, ViewParametersModel> serviceViewParametersModels = _viewParametersManager.getServiceViewParametersModels(skinId, zoneItem.getServiceId());
224        for (String viewName : serviceViewParametersModels.keySet())
225        {
226            ViewParametersModel serviceViewParameters = serviceViewParametersModels.get(viewName);
227            ModelAwareDataHolder serviceViewParametersHolder = zoneItem.getServiceViewParametersHolder(viewName);
228            
229            Map<String, Object> serviceValues = _parametersManager.getParametersValues(serviceViewParameters.getModelItems(), serviceViewParametersHolder, "");
230            String prefix = ViewParametersDAO.PREFIX_SERVICE + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR + _viewParametersManager.normalizeViewName(viewName) + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR;
231            values.putAll(_parametersManager.addPrefixToParameters(serviceValues, prefix));
232        }
233        
234        return values;
235    }
236    
237    /**
238     * Add the service to the given zone on given page
239     * @param pageId The page identifier
240     * @param zoneName The zone name
241     * @param serviceId The identifier of the service to add
242     * @param parameterValues the service parameter values. Can be empty
243     * @return The result with the identifiers of updated page, zone and zone item
244     * @throws IOException if an error occurred while saving parameters
245     */
246    @Callable
247    public Map<String, Object> addService(String pageId, String zoneName, String serviceId, Map<String, Object> parameterValues) throws IOException
248    {
249        if (StringUtils.isEmpty(serviceId) || StringUtils.isEmpty(pageId) || StringUtils.isEmpty(zoneName))
250        {
251            throw new IllegalArgumentException("ServiceId, PageId or ZoneName is missing");
252        }
253        
254        // Check the service
255        Service service = null;
256        try
257        {
258            service = _serviceExtensionPoint.getExtension(serviceId);
259        }
260        catch (IllegalArgumentException e)
261        {
262            throw new IllegalArgumentException("Service with id '" + serviceId + "' does not exist", e);
263        }
264        
265        try
266        {
267            SitemapElement sitemapElement = _resolver.resolveById(pageId);
268            if (!(sitemapElement instanceof ModifiableSitemapElement modifiableSitemapElement))
269            {
270                throw new IllegalArgumentException("Can not affect service on a non-modifiable page " + pageId);
271            }
272            
273            if (sitemapElement.getTemplate() == null)
274            {
275                throw new IllegalArgumentException("Can not affect service on a non-container page " + pageId);
276            }
277         
278            ModifiableZone zone;
279            if (modifiableSitemapElement.hasZone(zoneName))
280            {
281                zone = modifiableSitemapElement.getZone(zoneName);
282            }
283            else
284            {
285                zone = modifiableSitemapElement.createZone(zoneName);
286            }
287            
288            ModifiableZoneItem zoneItem = zone.addZoneItem();
289            zoneItem.setType(ZoneType.SERVICE);
290            zoneItem.setServiceId(serviceId);
291            
292            Map<String, List<I18nizableText>> allErrors = _setParameterValues(parameterValues, service, zoneItem);
293            
294            Map<String, Object> results = new HashMap<>();
295            if (!allErrors.isEmpty())
296            {
297                results.put("errors", allErrors);
298                return results;
299            }
300            
301            modifiableSitemapElement.saveChanges();
302            
303            Map<String, Object> eventParams = new HashMap<>();
304            eventParams.put(ObservationConstants.ARGS_SITEMAP_ELEMENT, sitemapElement);
305            eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItem.getId());
306            eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, ZoneType.SERVICE);
307            eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_SERVICE, serviceId);
308            _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_ADDED, _currentUserProvider.getUser(), eventParams));
309            
310            results.put("id", sitemapElement.getId());
311            results.put("zoneitem-id", zoneItem.getId());
312            results.put("zone-name", zone.getName());
313            
314            return results;
315        }
316        catch (UnknownAmetysObjectException e)
317        {
318            throw new IllegalArgumentException("An error occured adding the service '" + serviceId + "' on the page '" + pageId + "'", e);
319        }
320    }
321    
322    /**
323     * Edit the parameter values of the given service
324     * @param zoneItemId The identifier of the zone item holding the service
325     * @param serviceId The service identifier
326     * @param parameterValues the service parameter values to update
327     * @return The result with the identifiers of updated page, zone and zone item
328     * @throws IOException if an error occurs while saving parameters
329     */
330    @Callable
331    public Map<String, Object> editServiceParameterValues(String zoneItemId, String serviceId, Map<String, Object> parameterValues) throws IOException
332    {
333        Service service = null;
334        try
335        {
336            service = _serviceExtensionPoint.getExtension(serviceId);
337        }
338        catch (IllegalArgumentException e)
339        {
340            throw new IllegalArgumentException("Service with id '" + serviceId + "' does not exist", e);
341        }
342        
343        ZoneItem zoneItem = _resolver.resolveById(zoneItemId);
344        if (!(zoneItem instanceof ModifiableZoneItem))
345        {
346            throw new IllegalArgumentException("Can not configure service on a non-modifiable zone item " + zoneItemId);
347        }
348        
349        ModifiableZoneItem modifiableZoneItem = (ModifiableZoneItem) zoneItem;
350        
351        Map<String, List<I18nizableText>> allErrors = _setParameterValues(parameterValues, service, modifiableZoneItem);
352        
353        Map<String, Object> results = new HashMap<>();
354        if (!allErrors.isEmpty())
355        {
356            results.put("errors", allErrors);
357            return results;
358        }
359        
360        modifiableZoneItem.saveChanges();
361        
362        Map<String, Object> eventParams = new HashMap<>();
363        eventParams.put(ObservationConstants.ARGS_ZONE_ITEM, zoneItem);
364        eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItem.getId());
365        eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_SERVICE, serviceId);
366        _observationManager.notify(new Event(ObservationConstants.EVENT_SERVICE_MODIFIED, _currentUserProvider.getUser(), eventParams));
367        
368        results.put("id", zoneItem.getZone().getSitemapElement().getId());
369        results.put("zoneitem-id", zoneItem.getId());
370        results.put("zone-name", zoneItem.getZone().getName());
371        
372        return results;
373    }
374
375    /**
376     * Set the parameter values for the service (with view parameters)
377     * @param parameterValues the parameter values
378     * @param service the service
379     * @param zoneItem the zone item
380     * @return the map of error
381     */
382    protected Map<String, List<I18nizableText>> _setParameterValues(Map<String, Object> parameterValues, Service service, ModifiableZoneItem zoneItem)
383    {
384        ModifiableModelAwareDataHolder serviceDataHolder = zoneItem.getServiceParameters();
385        Map<String, ModelItem> definitions = service.getParameters();
386        Map<String, List<I18nizableText>> allErrors = _parametersManager.setParameterValues(serviceDataHolder, definitions.values(), parameterValues);
387        
388        if (parameterValues.containsKey(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME))
389        {
390            String viewName = (String) parameterValues.get(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME);
391            
392            String skinId = zoneItem.getZone()
393                    .getSitemapElement()
394                    .getSite()
395                    .getSkinId();
396            Optional<ViewParametersModel> serviceViewParametersModel = _viewParametersManager.getServiceViewParametersModel(skinId, service.getId(), viewName);
397            
398            if (serviceViewParametersModel.isPresent())
399            {
400                ModifiableModelAwareDataHolder serviceParametersHolder = zoneItem.getServiceViewParametersHolder(viewName);
401                String prefix = ViewParametersDAO.PREFIX_SERVICE + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR + _viewParametersManager.normalizeViewName(viewName) + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR;
402                Map<String, Object> viewParametersValues = _parametersManager.getParametersStartWithPrefix(parameterValues, prefix);
403                allErrors.putAll(_parametersManager.setParameterValues(serviceParametersHolder, serviceViewParametersModel.get().getModelItems(), viewParametersValues));
404            }
405        }
406        
407        return allErrors;
408    }
409}