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