001/*
002 *  Copyright 2015 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.plugins.maps;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Map.Entry;
025
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.logger.AbstractLogEnabled;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang.StringUtils;
032
033import org.ametys.core.observation.Event;
034import org.ametys.core.observation.ObservationManager;
035import org.ametys.core.ui.Callable;
036import org.ametys.core.user.CurrentUserProvider;
037import org.ametys.plugins.repository.AmetysObjectResolver;
038import org.ametys.plugins.repository.UnknownAmetysObjectException;
039import org.ametys.plugins.repository.metadata.CompositeMetadata;
040import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
041import org.ametys.runtime.parameter.Errors;
042import org.ametys.runtime.parameter.ParameterHelper;
043import org.ametys.runtime.parameter.Validator;
044import org.ametys.web.ObservationConstants;
045import org.ametys.web.repository.page.ModifiablePage;
046import org.ametys.web.repository.page.ModifiableZone;
047import org.ametys.web.repository.page.ModifiableZoneItem;
048import org.ametys.web.repository.page.Page;
049import org.ametys.web.repository.page.Page.PageType;
050import org.ametys.web.repository.page.ZoneItem;
051import org.ametys.web.repository.page.ZoneItem.ZoneType;
052import org.ametys.web.service.Service;
053import org.ametys.web.service.ServiceExtensionPoint;
054import org.ametys.web.service.ServiceParameter;
055import org.ametys.web.service.ServiceParameterOrRepeater;
056
057/**
058 * Helper gathering callable methods for the maps plugin.
059 */
060public class MapServiceHelper extends AbstractLogEnabled implements Serviceable, Component
061{
062    private AmetysObjectResolver _resolver;
063    private ServiceExtensionPoint _serviceExtensionPoint;
064    private CurrentUserProvider _currentUserProvider;
065    private ObservationManager _observationManager;
066
067    @Override
068    public void service(ServiceManager manager) throws ServiceException
069    {
070        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
071        _serviceExtensionPoint = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE);
072        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
073        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
074    }
075    
076    /**
077     * Get values of service parameters
078     * @param zoneItemId The zone item id
079     * @return The service parameters
080     */
081    @Callable
082    public Map<String, Object> getServiceParameters (String zoneItemId)
083    {
084        ModifiableZoneItem zoneItem = _resolver.resolveById(zoneItemId);
085        
086        Map<String, Object> values = new HashMap<>();
087        
088        ModifiableCompositeMetadata serviceParameters = zoneItem.getServiceParameters();
089        values.put("centerLat", serviceParameters.getString("centerLat", "0.0"));
090        values.put("centerLng", serviceParameters.getString("centerLng", "0.0"));
091        values.put("zoomLevel", serviceParameters.getString("zoomLevel", "7"));
092        values.put("mapTypeId", serviceParameters.getString("mapTypeId"));
093        
094        List<Map<String, Object>> pois = new ArrayList<>();
095        
096        CompositeMetadata poisMeta = serviceParameters.getCompositeMetadata("pois");
097        
098        for (String poiName : poisMeta.getMetadataNames())
099        {
100            CompositeMetadata poiMeta = poisMeta.getCompositeMetadata(poiName);
101            String type = poiMeta.getString("type");
102            
103            Map<String, Object> poi = new HashMap<>();
104            
105            poi.put("title", poiMeta.getString("title", ""));
106            poi.put("description", poiMeta.getString("description", ""));
107            poi.put("type", type);
108            
109            if ("marker".equals(type))
110            {
111                poi.put("icon", poiMeta.getString("icon", ""));
112                poi.put("lat", poiMeta.getString("lat", "0.0"));
113                poi.put("lng", poiMeta.getString("lng", "0.0"));
114            }
115            else if ("polygon".equals(type))
116            {
117                poi.put("color", poiMeta.getString("color", ""));
118                
119                List<Map<String, Object>> points = new ArrayList<>();
120                
121                CompositeMetadata pointsMeta = poiMeta.getCompositeMetadata("points");
122                
123                String[] metadataNames = pointsMeta.getMetadataNames();
124                Arrays.sort(metadataNames);
125                
126                for (String pointName : metadataNames)
127                {
128                    CompositeMetadata pointMeta = pointsMeta.getCompositeMetadata(pointName);
129                    
130                    Map<String, Object> point = new HashMap<>();
131                    
132                    point.put("lat", pointMeta.getDouble("lat", 0.0));
133                    point.put("lng", pointMeta.getDouble("lng", 0.0));
134                    
135                    points.add(point);
136                }
137                
138                poi.put("points", points);
139            }
140            else
141            {
142                throw new IllegalArgumentException("Unknown type value for POI " + type);
143            }
144            
145            pois.add(poi);
146        }
147        
148        values.put("pois", pois); 
149        
150        return values;
151        
152    }
153    
154    /**
155     * Set service
156     * @param pageId The page id
157     * @param serviceId The service id
158     * @param zoneName The name of zone of add service into
159     * @param parameters the service parameters. Can be empty
160     * @return The result with the ids of updated page, zone and zone item
161     * @throws IOException if an error occurred while saving parameters
162     */
163    @Callable
164    public Map<String, Object> setService (String pageId, String serviceId, String zoneName, Map<String, Object> parameters) throws IOException
165    {
166        if (StringUtils.isEmpty(serviceId) || StringUtils.isEmpty(pageId) || StringUtils.isEmpty(zoneName))
167        {
168            throw new IllegalArgumentException("ServiceId, PageId or ZoneName is missing");
169        }
170        
171        // Check the service
172        try
173        {
174            _serviceExtensionPoint.getExtension(serviceId);
175        }
176        catch (IllegalArgumentException e)
177        {
178            throw new IllegalArgumentException("Service with id '" + serviceId + "' does not exist", e);
179        }
180        
181        try
182        {
183            Page page = _resolver.resolveById(pageId);
184            if (!(page instanceof ModifiablePage))
185            {
186                throw new IllegalArgumentException("Can not affect service on a non-modifiable page " + pageId);
187            }
188            
189            ModifiablePage mPage = (ModifiablePage) page;
190            
191            if (page.getType() != PageType.CONTAINER)
192            {
193                throw new IllegalArgumentException("Can not affect service on a non-container page " + pageId);
194            }
195         
196            ModifiableZone zone;
197            if (page.hasZone(zoneName))
198            {
199                zone = mPage.getZone(zoneName);
200            }
201            else
202            {
203                zone = mPage.createZone(zoneName);
204            }
205            
206            ModifiableZoneItem zoneItem = zone.addZoneItem();
207            zoneItem.setType(ZoneType.SERVICE);
208            zoneItem.setServiceId(serviceId);
209            
210            ModifiableCompositeMetadata serviceMetadata = zoneItem.getServiceParameters();
211            Service service = _serviceExtensionPoint.getExtension(serviceId);
212            
213            Map<String, Errors> allErrors = new HashMap<>();
214            _setParameterValues(serviceMetadata, service, parameters, allErrors);
215            
216            Map<String, Object> results = new HashMap<>();
217            if (!allErrors.isEmpty())
218            {
219                Map<String, Object> errors = new HashMap<>();
220                
221                for (Entry<String, Errors> errorEntry : allErrors.entrySet())
222                {
223                    String paramId = errorEntry.getKey();
224                    errors.put(paramId, errorEntry.getValue().getErrors());
225                }
226                
227                results.put("errors", errors);
228            }
229            else
230            {
231                mPage.saveChanges();
232                
233                Map<String, Object> eventParams = new HashMap<>();
234                eventParams.put(ObservationConstants.ARGS_PAGE, page);
235                eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItem.getId());
236                eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, ZoneType.SERVICE);
237                _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_ADDED, _currentUserProvider.getUser(), eventParams));
238                
239                results.put("id", page.getId());
240                results.put("zoneitem-id", zoneItem.getId());
241                results.put("zone-name", zone.getName());
242            }
243            
244            return results;
245        }
246        catch (UnknownAmetysObjectException e)
247        {
248            throw new IllegalArgumentException("An error occured adding the service '" + serviceId + "' on the page '" + pageId + "'", e);
249        }
250    }
251    
252    /**
253     * Configure service
254     * @param serviceId The service id
255     * @param zoneItemId The id of aone item holding this service
256     * @param parameters the service parameters to update
257     * @return The result with the ids 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> configureService (String serviceId, String zoneItemId, Map<String, Object> parameters) throws IOException
262    {
263        Service service = null;
264        
265        try
266        {
267            service = _serviceExtensionPoint.getExtension(serviceId);
268        }
269        catch (IllegalArgumentException e)
270        {
271            throw new IllegalArgumentException("Service with id '" + serviceId + "' does not exist", e);
272        }
273        
274        ZoneItem zoneItem = _resolver.resolveById(zoneItemId);
275        if (!(zoneItem instanceof ModifiableZoneItem))
276        {
277            throw new IllegalArgumentException("Can not configure service on a non-modifiable zone item " + zoneItemId);
278        }
279        
280        ModifiableZoneItem mZoneItem = (ModifiableZoneItem) zoneItem;
281        
282        ModifiableCompositeMetadata serviceMetadata = mZoneItem.getServiceParameters();
283        
284        Map<String, Errors> allErrors = new HashMap<>();
285        _setParameterValues(serviceMetadata, service, parameters, allErrors);
286        
287        Map<String, Object> results = new HashMap<>();
288        
289        if (!allErrors.isEmpty())
290        {
291            Map<String, Object> errors = new HashMap<>();
292            
293            for (Entry<String, Errors> errorEntry : allErrors.entrySet())
294            {
295                String paramId = errorEntry.getKey();
296                errors.put(paramId, errorEntry.getValue().getErrors());
297            }
298            
299            results.put("errors", errors);
300        }
301        else
302        {
303            mZoneItem.saveChanges();
304            
305            Map<String, Object> eventParams = new HashMap<>();
306            eventParams.put(ObservationConstants.ARGS_ZONE_ITEM, zoneItem);
307            _observationManager.notify(new Event(ObservationConstants.EVENT_SERVICE_MODIFIED, _currentUserProvider.getUser(), eventParams));
308            
309            results.put("id", zoneItem.getZone().getPage().getId());
310            results.put("zoneitem-id", zoneItem.getId());
311            results.put("zone-name", zoneItem.getZone().getName());
312        }
313        
314        return results;
315    }
316    
317    private void _setParameterValues(ModifiableCompositeMetadata serviceMetadata, Service service, Map<String, Object> values, Map<String, Errors> allErrors)
318    {
319        // Save default parameters
320        for (ServiceParameterOrRepeater param : service.getParameters().values())
321        {
322            if (param instanceof ServiceParameter)
323            {
324                _getAndSaveParameter("service.param.", (ServiceParameter) param, values, serviceMetadata, allErrors);
325            }
326        }
327        
328        // Get additional parameters for Map configuration and Point Of Interests
329        String centerLatStr = (String) values.get("map-service-center-lat");
330        String centerLngStr = (String) values.get("map-service-center-lng");
331        int zoomLevel = (Integer) values.get("map-service-zoom-level");
332        String mapTypeId = (String) values.get("map-service-map-type-id");
333        
334        serviceMetadata.setMetadata("centerLat", Double.parseDouble(centerLatStr)); 
335        serviceMetadata.setMetadata("centerLng", Double.parseDouble(centerLngStr)); 
336        serviceMetadata.setMetadata("zoomLevel", zoomLevel);
337        serviceMetadata.setMetadata("mapTypeId", mapTypeId);
338        
339        @SuppressWarnings("unchecked")
340        List<Map<String, Object>> pois = (List<Map<String, Object>>) values.get("pois");
341        
342        if (serviceMetadata.hasMetadata("pois"))
343        {
344            // Remove old POIs
345            serviceMetadata.removeMetadata("pois");
346        }
347        
348        ModifiableCompositeMetadata poisMetadata = serviceMetadata.getCompositeMetadata("pois", true);
349        int count = 1;
350        
351        if (pois != null)
352        {
353            for (Map<String, Object> poi : pois)
354            {
355                ModifiableCompositeMetadata poiMetadata = poisMetadata.getCompositeMetadata("poi-" + count++, true);
356                String type = (String) poi.get("gtype");
357                poiMetadata.setMetadata("type", type);
358                
359                String poiTitle = (String) poi.get("title");
360                String poiDesc = (String) poi.get("description");
361                
362                poiMetadata.setMetadata("title", poiTitle);
363                poiMetadata.setMetadata("description", poiDesc);
364                
365                if ("marker".equals(type))
366                {
367                    String poiIcon = (String) poi.get("icon");
368                    double poiLat = ((Number) poi.get("lat")).doubleValue();
369                    double poiLng = ((Number) poi.get("lng")).doubleValue();
370                    
371                    poiMetadata.setMetadata("icon", poiIcon);
372                    poiMetadata.setMetadata("lat", poiLat);
373                    poiMetadata.setMetadata("lng", poiLng);
374                }
375                else if ("polygon".equals(type))
376                {
377                    String poiColor = (String) poi.get("color");
378                    poiMetadata.setMetadata("color", poiColor);
379                    
380                    @SuppressWarnings("unchecked")
381                    List<Map<String, Number>> points = (List<Map<String, Number>>) poi.get("points");
382                    
383                    ModifiableCompositeMetadata pointsMetadata = poiMetadata.getCompositeMetadata("points", true);
384                    int count2 = 1;
385                    
386                    for (Map<String, Number> point : points)
387                    {
388                        // 4-digit number
389                        String strCount = StringUtils.leftPad(String.valueOf(count2++), 4, '0');
390                        ModifiableCompositeMetadata pointMetadata = pointsMetadata.getCompositeMetadata("point-" + strCount, true);
391                        
392                        double pointLat = point.get("lat").doubleValue();
393                        double pointLng = point.get("lng").doubleValue();
394                        
395                        pointMetadata.setMetadata("lat", pointLat);
396                        pointMetadata.setMetadata("lng", pointLng);
397                    }
398                }
399                else
400                {
401                    throw new IllegalArgumentException("Unknown POI type " + type);
402                }
403            }
404        }
405    }
406    
407    private void _getAndSaveParameter (String prefix, ServiceParameter param, Map<String, Object> formValues, ModifiableCompositeMetadata metadataHolder, Map<String, Errors> allErrors)
408    {
409        Object objectValue = formValues.get(prefix + param.getId());
410        
411        Validator validator = param.getValidator();
412        
413        if (validator != null)
414        {
415            Errors errors = new Errors();
416            
417            if (objectValue != null && objectValue instanceof List && !param.isMultiple())
418            {
419                Object singleValue = null;
420                if (((List) objectValue).size() > 0)
421                {
422                    singleValue = ((List) objectValue).get(0);
423                }
424                
425                validator.validate(singleValue, errors);
426            }
427            else
428            {
429                validator.validate(objectValue, errors);
430            }
431            
432            if (errors.hasErrors())
433            {
434                allErrors.put(param.getId(), errors);
435                return;
436            }
437        }
438        
439        if (objectValue != null)
440        {
441            if (param.isMultiple())
442            {
443                List<String> stringValues = new ArrayList<>();
444                if (objectValue instanceof List)
445                {
446                    int length = ((List) objectValue).size();
447                    for (int i = 0; i < length; i++)
448                    {
449                        stringValues.add(ParameterHelper.valueToString(((List) objectValue).get(i)));
450                    }
451                }
452                else
453                {
454                    stringValues.add(ParameterHelper.valueToString(objectValue));
455                }
456                metadataHolder.setMetadata(param.getId(), stringValues.toArray(new String[stringValues.size()]));
457            }
458            else
459            {
460                Object singleValue = objectValue;
461                if (objectValue instanceof List)
462                {
463                    if (((List) objectValue).size() > 0)
464                    {
465                        singleValue = ((List) objectValue).get(0);
466                    }
467                }
468                metadataHolder.setMetadata(param.getId(), ParameterHelper.valueToString(singleValue));
469            }
470        }
471    }
472}