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