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; 024import java.util.stream.Collectors; 025 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.context.Context; 028import org.apache.avalon.framework.context.ContextException; 029import org.apache.avalon.framework.context.Contextualizable; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.cocoon.ProcessingException; 034import org.apache.cocoon.components.ContextHelper; 035import org.apache.cocoon.environment.Request; 036import org.apache.commons.lang.StringUtils; 037import org.apache.commons.lang3.tuple.ImmutablePair; 038import org.apache.commons.lang3.tuple.Pair; 039 040import org.ametys.core.observation.Event; 041import org.ametys.core.observation.ObservationManager; 042import org.ametys.core.ui.Callable; 043import org.ametys.core.user.CurrentUserProvider; 044import org.ametys.plugins.repository.AmetysObjectResolver; 045import org.ametys.plugins.repository.UnknownAmetysObjectException; 046import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 047import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder; 048import org.ametys.runtime.i18n.I18nizableText; 049import org.ametys.runtime.model.DefinitionContext; 050import org.ametys.runtime.model.ModelItem; 051import org.ametys.runtime.model.View; 052import org.ametys.runtime.model.ViewHelper; 053import org.ametys.runtime.model.ViewItem; 054import org.ametys.runtime.model.ViewItemContainer; 055import org.ametys.runtime.model.disableconditions.DisableCondition.OPERATOR; 056import org.ametys.runtime.plugin.component.AbstractLogEnabled; 057import org.ametys.web.ObservationConstants; 058import org.ametys.web.WebConstants; 059import org.ametys.web.parameters.ParametersManager; 060import org.ametys.web.parameters.view.ViewParametersDAO; 061import org.ametys.web.parameters.view.ViewParametersManager; 062import org.ametys.web.parameters.view.ViewParametersModel; 063import org.ametys.web.repository.page.ZoneItem.ZoneType; 064import org.ametys.web.service.Service; 065import org.ametys.web.service.ServiceExtensionPoint; 066 067/** 068 * Class containing callables to retrieve and configure service parameters 069 */ 070public class ZoneItemManager extends AbstractLogEnabled implements Component, Serviceable, Contextualizable 071{ 072 /** Avalon Role */ 073 public static final String ROLE = ZoneItemManager.class.getName(); 074 075 /** Constant for untouched binary metadata. */ 076 protected static final String __SERVICE_PARAM_UNTOUCHED_BINARY = "untouched"; 077 078 private ServiceExtensionPoint _serviceExtensionPoint; 079 private AmetysObjectResolver _resolver; 080 private ObservationManager _observationManager; 081 private CurrentUserProvider _currentUserProvider; 082 private ParametersManager _parametersManager; 083 private ViewParametersManager _viewParametersManager; 084 private Context _context; 085 private PageDAO _pageDAO; 086 087 public void service(ServiceManager manager) throws ServiceException 088 { 089 _serviceExtensionPoint = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE); 090 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 091 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 092 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 093 _parametersManager = (ParametersManager) manager.lookup(ParametersManager.ROLE); 094 _viewParametersManager = (ViewParametersManager) manager.lookup(ViewParametersManager.ROLE); 095 _pageDAO = (PageDAO) manager.lookup(PageDAO.ROLE); 096 } 097 098 public void contextualize(Context context) throws ContextException 099 { 100 _context = context; 101 } 102 103 /** 104 * Retrieves the parameter definitions of the given service 105 * @param serviceId Identifier of the service 106 * @param pageId the page id 107 * @param zoneItemId the zone item id 108 * @param zoneName the zone name 109 * @return the parameter definitions 110 * @throws ProcessingException if an error occurs 111 */ 112 @Callable 113 public Map<String, Object> getServiceParameterDefinitions(String serviceId, String pageId, String zoneItemId, String zoneName) throws ProcessingException 114 { 115 _setRequestAttribute(serviceId, pageId, zoneItemId, zoneName); 116 117 Map<String, Object> response = new HashMap<>(); 118 119 Service service = _serviceExtensionPoint.getExtension(serviceId); 120 121 response.put("id", serviceId); 122 response.put("url", service.getURL()); 123 response.put("label", service.getLabel()); 124 response.put("height", service.getCreationBoxHeight()); 125 response.put("width", service.getCreationBoxWidth()); 126 127 // Clone the view to not record in the service view the added view parameters 128 View clonedView = _cloneView(service.getView()); 129 130 SitemapElement sitemapElement = _resolver.resolveById(pageId); 131 132 Map<String, ViewParametersModel> serviceViewParametersModels = _viewParametersManager.getServiceViewParametersModels(sitemapElement.getSite().getSkinId(), serviceId); 133 134 Pair<ViewItemContainer, Integer> containerAndIndex = _getXSLTViewItemContainerAndIndex(clonedView); 135 if (containerAndIndex != null) 136 { 137 for (String viewName : serviceViewParametersModels.keySet()) 138 { 139 ViewParametersModel viewParameters = serviceViewParametersModels.get(viewName); 140 Collection< ? extends ModelItem> modelItems = viewParameters.getModelItems(); 141 142 String prefix = ViewParametersDAO.PREFIX_SERVICE + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR + _viewParametersManager.normalizeViewName(viewName) + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR; 143 Optional<Integer> index = Optional.of(containerAndIndex.getRight() + 1); //add 1 to the XSLT parameter index to include the view parameters after the XSLT one 144 List<ModelItem> includeModelItems = _viewParametersManager.includeModelItems(modelItems, prefix, containerAndIndex.getLeft(), index); //add 1 to the XSL 145 _viewParametersManager.setDisableConditions(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME, OPERATOR.NEQ, viewName, includeModelItems); 146 } 147 } 148 149 response.put("parameters", clonedView.toJSON(DefinitionContext.newInstance().withEdition(true))); 150 151 return response; 152 } 153 154 private View _cloneView(View serviceView) 155 { 156 View clonedView = new View(); 157 serviceView.copyTo(clonedView); 158 clonedView.addViewItems(ViewHelper.copyViewItems(serviceView.getViewItems())); 159 return clonedView; 160 } 161 162 private Pair<ViewItemContainer, Integer> _getXSLTViewItemContainerAndIndex(ViewItemContainer viewItemContainer) 163 { 164 List<ViewItem> viewItems = viewItemContainer.getViewItems(); 165 for (int i = 0; i < viewItems.size(); i++) 166 { 167 ViewItem viewItem = viewItems.get(i); 168 169 if (ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME.equals(viewItem.getName())) 170 { 171 return ImmutablePair.of(viewItemContainer, i); 172 } 173 else if (viewItem instanceof ViewItemContainer) 174 { 175 Pair<ViewItemContainer, Integer> xsltContainerAndIndex = _getXSLTViewItemContainerAndIndex((ViewItemContainer) viewItem); 176 if (xsltContainerAndIndex != null) 177 { 178 return xsltContainerAndIndex; 179 } 180 } 181 } 182 183 return null; 184 } 185 186 private void _setRequestAttribute(String serviceId, String pageId, String zoneItemId, String zoneName) 187 { 188 Request request = ContextHelper.getRequest(_context); 189 190 request.setAttribute(WebConstants.REQUEST_ATTR_SERVICE_ID, serviceId); 191 request.setAttribute(WebConstants.REQUEST_ATTR_PAGE_ID, pageId); 192 request.setAttribute(WebConstants.REQUEST_ATTR_ZONEITEM_ID, zoneItemId); 193 request.setAttribute(WebConstants.REQUEST_ATTR_ZONE_NAME, zoneName); 194 } 195 196 /** 197 * Get the service parameter values 198 * @param zoneItemId the zone item id 199 * @param serviceId the service Id 200 * @return the values 201 */ 202 @Callable 203 public Map<String, Object> getServiceParameterValues(String zoneItemId, String serviceId) 204 { 205 Service service = _serviceExtensionPoint.getExtension(serviceId); 206 ZoneItem zoneItem = _resolver.resolveById(zoneItemId); 207 Zone zone = zoneItem.getZone(); 208 209 _setRequestAttribute(serviceId, zone.getSitemapElement().getId(), zoneItemId, zone.getName()); 210 211 Map<String, Object> response = new HashMap<>(); 212 Collection<ModelItem> serviceParameterDefinitions = service.getParameters().values(); 213 ModelAwareDataHolder dataHolder = zoneItem.getServiceParameters(); 214 215 Map<String, Object> values = _parametersManager.getParametersValues(serviceParameterDefinitions, dataHolder, ""); 216 values.putAll(_getServiceViewParametersValues(zoneItem)); 217 218 response.put("values", values); 219 220 List<Map<String, Object>> repeaters = _parametersManager.getRepeatersValues(serviceParameterDefinitions, dataHolder, ""); 221 response.put("repeaters", repeaters); 222 223 return response; 224 } 225 226 /** 227 * Get the service view parameters values 228 * @param zoneItem the zone item 229 * @return the values 230 */ 231 protected Map<String, Object> _getServiceViewParametersValues(ZoneItem zoneItem) 232 { 233 Map<String, Object> values = new HashMap<>(); 234 235 String skinId = zoneItem.getZone() 236 .getSitemapElement() 237 .getSite() 238 .getSkinId(); 239 240 Map<String, ViewParametersModel> serviceViewParametersModels = _viewParametersManager.getServiceViewParametersModels(skinId, zoneItem.getServiceId()); 241 for (String viewName : serviceViewParametersModels.keySet()) 242 { 243 ViewParametersModel serviceViewParameters = serviceViewParametersModels.get(viewName); 244 ModelAwareDataHolder serviceViewParametersHolder = zoneItem.getServiceViewParametersHolder(viewName); 245 246 Map<String, Object> serviceValues = _parametersManager.getParametersValues(serviceViewParameters.getModelItems(), serviceViewParametersHolder, ""); 247 String prefix = ViewParametersDAO.PREFIX_SERVICE + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR + _viewParametersManager.normalizeViewName(viewName) + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR; 248 values.putAll(_parametersManager.addPrefixToParameters(serviceValues, prefix)); 249 } 250 251 return values; 252 } 253 254 /** 255 * Add the service to the given zone on given page 256 * @param pageId The page identifier 257 * @param zoneName The zone name 258 * @param serviceId The identifier of the service to add 259 * @param parameterValues the service parameter values. Can be empty 260 * @return The result with the identifiers of updated page, zone and zone item 261 * @throws IOException if an error occurred while saving parameters 262 */ 263 @Callable 264 public Map<String, Object> addService(String pageId, String zoneName, String serviceId, Map<String, Object> parameterValues) throws IOException 265 { 266 if (StringUtils.isEmpty(serviceId) || StringUtils.isEmpty(pageId) || StringUtils.isEmpty(zoneName)) 267 { 268 throw new IllegalArgumentException("ServiceId, PageId or ZoneName is missing"); 269 } 270 271 // Check the service 272 Service service = null; 273 try 274 { 275 service = _serviceExtensionPoint.getExtension(serviceId); 276 } 277 catch (IllegalArgumentException e) 278 { 279 throw new IllegalArgumentException("Service with id '" + serviceId + "' does not exist", e); 280 } 281 282 try 283 { 284 SitemapElement sitemapElement = _resolver.resolveById(pageId); 285 if (!(sitemapElement instanceof ModifiableSitemapElement modifiableSitemapElement)) 286 { 287 throw new IllegalArgumentException("Can not affect service on a non-modifiable page " + pageId); 288 } 289 290 if (sitemapElement instanceof LockablePage lockablePage && lockablePage.isLocked()) 291 { 292 throw new IllegalArgumentException("Can not affect service on a locked page " + pageId); 293 } 294 295 if (sitemapElement.getTemplate() == null) 296 { 297 throw new IllegalArgumentException("Can not affect service on a non-container page " + pageId); 298 } 299 300 if (!_getAllowedServicesId(pageId, zoneName).contains(serviceId)) 301 { 302 throw new IllegalArgumentException("Can not affect service '" + serviceId + "' on a zone '" + zoneName + "'"); 303 } 304 305 ModifiableZone zone; 306 if (modifiableSitemapElement.hasZone(zoneName)) 307 { 308 zone = modifiableSitemapElement.getZone(zoneName); 309 } 310 else 311 { 312 zone = modifiableSitemapElement.createZone(zoneName); 313 } 314 315 ModifiableZoneItem zoneItem = zone.addZoneItem(); 316 zoneItem.setType(ZoneType.SERVICE); 317 zoneItem.setServiceId(serviceId); 318 319 Map<String, List<I18nizableText>> allErrors = _setParameterValues(parameterValues, service, zoneItem); 320 321 Map<String, Object> results = new HashMap<>(); 322 if (!allErrors.isEmpty()) 323 { 324 results.put("errors", allErrors); 325 return results; 326 } 327 328 modifiableSitemapElement.saveChanges(); 329 330 Map<String, Object> eventParams = new HashMap<>(); 331 eventParams.put(ObservationConstants.ARGS_SITEMAP_ELEMENT, sitemapElement); 332 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItem.getId()); 333 eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, ZoneType.SERVICE); 334 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_SERVICE, serviceId); 335 _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_ADDED, _currentUserProvider.getUser(), eventParams)); 336 337 results.put("id", sitemapElement.getId()); 338 results.put("zoneitem-id", zoneItem.getId()); 339 results.put("zone-name", zone.getName()); 340 341 return results; 342 } 343 catch (UnknownAmetysObjectException e) 344 { 345 throw new IllegalArgumentException("An error occured adding the service '" + serviceId + "' on the page '" + pageId + "'", e); 346 } 347 } 348 349 private List<String> _getAllowedServicesId(String pageId, String zoneName) 350 { 351 return _pageDAO.getAvailableServices(pageId, zoneName).stream() 352 .map(info -> (String) info.get("id")) 353 .collect(Collectors.toList()); 354 } 355 356 /** 357 * Edit the parameter values of the given service 358 * @param zoneItemId The identifier of the zone item holding the service 359 * @param serviceId The service identifier 360 * @param parameterValues the service parameter values to update 361 * @return The result with the identifiers of updated page, zone and zone item 362 * @throws IOException if an error occurs while saving parameters 363 */ 364 @Callable 365 public Map<String, Object> editServiceParameterValues(String zoneItemId, String serviceId, Map<String, Object> parameterValues) throws IOException 366 { 367 Service service = null; 368 try 369 { 370 service = _serviceExtensionPoint.getExtension(serviceId); 371 } 372 catch (IllegalArgumentException e) 373 { 374 throw new IllegalArgumentException("Service with id '" + serviceId + "' does not exist", e); 375 } 376 377 ZoneItem zoneItem = _resolver.resolveById(zoneItemId); 378 if (!(zoneItem instanceof ModifiableZoneItem)) 379 { 380 throw new IllegalArgumentException("Can not configure service on a non-modifiable zone item " + zoneItemId); 381 } 382 383 if (zoneItem.getZone().getSitemapElement() instanceof LockablePage lockablePage && lockablePage.isLocked()) 384 { 385 throw new IllegalArgumentException("Can not configure service on a locked page '/" + lockablePage.getSitemapName() + "/" + lockablePage.getPathInSitemap() + "'"); 386 } 387 388 ModifiableZoneItem modifiableZoneItem = (ModifiableZoneItem) zoneItem; 389 390 Map<String, List<I18nizableText>> allErrors = _setParameterValues(parameterValues, service, modifiableZoneItem); 391 392 Map<String, Object> results = new HashMap<>(); 393 if (!allErrors.isEmpty()) 394 { 395 results.put("errors", allErrors); 396 return results; 397 } 398 399 modifiableZoneItem.saveChanges(); 400 401 Map<String, Object> eventParams = new HashMap<>(); 402 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM, zoneItem); 403 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItem.getId()); 404 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_SERVICE, serviceId); 405 _observationManager.notify(new Event(ObservationConstants.EVENT_SERVICE_MODIFIED, _currentUserProvider.getUser(), eventParams)); 406 407 results.put("id", zoneItem.getZone().getSitemapElement().getId()); 408 results.put("zoneitem-id", zoneItem.getId()); 409 results.put("zone-name", zoneItem.getZone().getName()); 410 411 return results; 412 } 413 414 /** 415 * Set the parameter values for the service (with view parameters) 416 * @param parameterValues the parameter values 417 * @param service the service 418 * @param zoneItem the zone item 419 * @return the map of error 420 */ 421 protected Map<String, List<I18nizableText>> _setParameterValues(Map<String, Object> parameterValues, Service service, ModifiableZoneItem zoneItem) 422 { 423 ModifiableModelAwareDataHolder serviceDataHolder = zoneItem.getServiceParameters(); 424 Map<String, ModelItem> definitions = service.getParameters(); 425 Map<String, List<I18nizableText>> allErrors = _parametersManager.setParameterValues(serviceDataHolder, definitions.values(), parameterValues); 426 427 if (parameterValues.containsKey(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME)) 428 { 429 String viewName = (String) parameterValues.get(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME); 430 431 String skinId = zoneItem.getZone() 432 .getSitemapElement() 433 .getSite() 434 .getSkinId(); 435 Optional<ViewParametersModel> serviceViewParametersModel = _viewParametersManager.getServiceViewParametersModel(skinId, service.getId(), viewName); 436 437 if (serviceViewParametersModel.isPresent()) 438 { 439 ModifiableModelAwareDataHolder serviceParametersHolder = zoneItem.getServiceViewParametersHolder(viewName); 440 String prefix = ViewParametersDAO.PREFIX_SERVICE + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR + _viewParametersManager.normalizeViewName(viewName) + ViewParametersDAO.MODEL_ITEM_NAME_SEPARATOR; 441 Map<String, Object> viewParametersValues = _parametersManager.getParametersStartWithPrefix(parameterValues, prefix); 442 allErrors.putAll(_parametersManager.setParameterValues(serviceParametersHolder, serviceViewParametersModel.get().getModelItems(), viewParametersValues)); 443 } 444 } 445 446 return allErrors; 447 } 448 449 /** 450 * Paste the given service in the given zone 451 * @param srcZoneItemId The identifier of the zone item holding the original service 452 * @param serviceId The service identifier 453 * @param targetPageId The identifier of the page where to paste the service 454 * @param targetZoneName The zone name where to paste the service 455 * @return The result with the identifiers of updated page, zone and zone item 456 * @throws IOException if an error occurs while saving parameters 457 */ 458 @Callable 459 public Map<String, Object> pasteService(String srcZoneItemId, String serviceId, String targetPageId, String targetZoneName) throws IOException 460 { 461 if (StringUtils.isEmpty(serviceId) || StringUtils.isEmpty(targetPageId) || StringUtils.isEmpty(targetZoneName)) 462 { 463 throw new IllegalArgumentException("ServiceId, PageId or ZoneName is missing"); 464 } 465 466 // Check the service 467 try 468 { 469 _serviceExtensionPoint.getExtension(serviceId); 470 } 471 catch (IllegalArgumentException e) 472 { 473 throw new IllegalArgumentException("Service with id '" + serviceId + "' does not exist", e); 474 } 475 476 try 477 { 478 SitemapElement sitemapElement = _resolver.resolveById(targetPageId); 479 if (!(sitemapElement instanceof ModifiableSitemapElement modifiableSitemapElement)) 480 { 481 throw new IllegalArgumentException("Can not affect service on a non-modifiable page " + targetPageId); 482 } 483 484 if (sitemapElement instanceof LockablePage lockablePage && lockablePage.isLocked()) 485 { 486 throw new IllegalArgumentException("Can not affect service on a locked page " + targetPageId); 487 } 488 489 if (sitemapElement.getTemplate() == null) 490 { 491 throw new IllegalArgumentException("Can not affect service on a non-container page " + targetPageId); 492 } 493 494 if (!_getAllowedServicesId(targetPageId, targetZoneName).contains(serviceId)) 495 { 496 throw new IllegalArgumentException("Can not affect service '" + serviceId + "' on a zone '" + targetZoneName + "'"); 497 } 498 499 ModifiableZone zone; 500 if (modifiableSitemapElement.hasZone(targetZoneName)) 501 { 502 zone = modifiableSitemapElement.getZone(targetZoneName); 503 } 504 else 505 { 506 zone = modifiableSitemapElement.createZone(targetZoneName); 507 } 508 509 ModifiableZoneItem zoneItem = zone.addZoneItem(); 510 zoneItem.setType(ZoneType.SERVICE); 511 zoneItem.setServiceId(serviceId); 512 513 ZoneItem srcService = _resolver.resolveById(srcZoneItemId); 514 ModelAwareDataHolder serviceDataHolder = srcService.getServiceParameters(); 515 serviceDataHolder.copyTo(zoneItem.getServiceParameters()); 516 517 if (serviceDataHolder.hasDefinition(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME)) 518 { 519 String viewName = serviceDataHolder.getValue(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME); 520 521 // Copy of view parameters 522 srcService.getServiceViewParametersHolder(viewName).copyTo(zoneItem.getServiceViewParametersHolder(viewName)); 523 } 524 Map<String, Object> results = new HashMap<>(); 525 526 modifiableSitemapElement.saveChanges(); 527 528 Map<String, Object> eventParams = new HashMap<>(); 529 eventParams.put(ObservationConstants.ARGS_SITEMAP_ELEMENT, sitemapElement); 530 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItem.getId()); 531 eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, ZoneType.SERVICE); 532 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_SERVICE, serviceId); 533 _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_ADDED, _currentUserProvider.getUser(), eventParams)); 534 535 results.put("id", sitemapElement.getId()); 536 results.put("zoneitem-id", zoneItem.getId()); 537 results.put("zone-name", zone.getName()); 538 539 return results; 540 } 541 catch (UnknownAmetysObjectException e) 542 { 543 throw new IllegalArgumentException("An error occured adding the service '" + serviceId + "' on the page '" + targetPageId + "'", e); 544 } 545 } 546}