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