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