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.web.repository.page; 017 018import java.util.HashMap; 019import java.util.Map; 020import java.util.Set; 021 022import javax.jcr.RepositoryException; 023 024import org.apache.avalon.framework.component.Component; 025import org.apache.avalon.framework.logger.AbstractLogEnabled; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.avalon.framework.service.Serviceable; 029import org.apache.commons.lang.ArrayUtils; 030import org.apache.commons.lang.StringUtils; 031 032import org.ametys.cms.contenttype.ContentType; 033import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 034import org.ametys.cms.repository.Content; 035import org.ametys.cms.repository.DefaultContent; 036import org.ametys.cms.repository.WorkflowAwareContent; 037import org.ametys.core.observation.Event; 038import org.ametys.core.observation.ObservationManager; 039import org.ametys.core.right.RightManager; 040import org.ametys.core.right.RightManager.RightResult; 041import org.ametys.core.ui.Callable; 042import org.ametys.core.user.CurrentUserProvider; 043import org.ametys.core.user.UserIdentity; 044import org.ametys.plugins.repository.AmetysObjectResolver; 045import org.ametys.plugins.repository.AmetysRepositoryException; 046import org.ametys.plugins.repository.UnknownAmetysObjectException; 047import org.ametys.plugins.repository.jcr.JCRAmetysObject; 048import org.ametys.plugins.workflow.support.WorkflowProvider; 049import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 050import org.ametys.web.ObservationConstants; 051import org.ametys.web.WebConstants; 052import org.ametys.web.repository.content.SharedContent; 053import org.ametys.web.repository.content.WebContent; 054import org.ametys.web.repository.content.jcr.DefaultSharedContent; 055import org.ametys.web.repository.content.shared.SharedContentManager; 056import org.ametys.web.repository.page.Page.PageType; 057import org.ametys.web.repository.page.ZoneItem.ZoneType; 058import org.ametys.web.service.Service; 059import org.ametys.web.service.ServiceExtensionPoint; 060 061/** 062 * DAO for manipulating {@link Zone} and {@link ZoneItem} 063 * 064 */ 065public class ZoneDAO extends AbstractLogEnabled implements Serviceable, Component 066{ 067 /** Avalon Role */ 068 public static final String ROLE = ZoneDAO.class.getName(); 069 070 private AmetysObjectResolver _resolver; 071 private ObservationManager _observationManager; 072 private CurrentUserProvider _currentUserProvider; 073 private WorkflowProvider _workflowProvider; 074 private SharedContentManager _sharedContentManager; 075 private ContentTypesAssignmentHandler _cTypesAssignmentHandler; 076 private ServicesAssignmentHandler _serviceAssignmentHandler; 077 private ContentTypeExtensionPoint _cTypeEP; 078 private ServiceExtensionPoint _serviceEP; 079 private RightManager _rightManager; 080 081 @Override 082 public void service(ServiceManager smanager) throws ServiceException 083 { 084 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 085 _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); 086 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 087 _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE); 088 _sharedContentManager = (SharedContentManager) smanager.lookup(SharedContentManager.ROLE); 089 _cTypesAssignmentHandler = (ContentTypesAssignmentHandler) smanager.lookup(ContentTypesAssignmentHandler.ROLE); 090 _serviceAssignmentHandler = (ServicesAssignmentHandler) smanager.lookup(ServicesAssignmentHandler.ROLE); 091 _cTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 092 _serviceEP = (ServiceExtensionPoint) smanager.lookup(ServiceExtensionPoint.ROLE); 093 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 094 } 095 096 /** 097 * Insert a shared content into a zone 098 * @param pageId The page id 099 * @param zoneName The zone name 100 * @param contentId The content id to insert 101 * @param metadataSetName The metadata set name 102 * @return The result map 103 */ 104 @Callable 105 public Map<String, Object> addSharedContent (String pageId, String zoneName, String contentId, String metadataSetName) 106 { 107 Map<String, Object> result = new HashMap<>(); 108 109 Page page = _resolver.resolveById(pageId); 110 111 if (!(page instanceof ModifiablePage)) 112 { 113 throw new IllegalArgumentException("Can not add a shared content on a non-modifiable page " + pageId); 114 } 115 116 if (page.getType() != PageType.CONTAINER) 117 { 118 throw new IllegalArgumentException("Page '" + pageId + "' is not a container page: unable to add a shared content"); 119 } 120 121 ModifiablePage mPage = (ModifiablePage) page; 122 123 ModifiableZone zone; 124 if (mPage.hasZone(zoneName)) 125 { 126 zone = mPage.getZone(zoneName); 127 } 128 else 129 { 130 zone = mPage.createZone(zoneName); 131 } 132 133 Content content = _resolver.resolveById(contentId); 134 String contentSiteName = null; 135 if (content instanceof WebContent) 136 { 137 contentSiteName = ((WebContent) content).getSiteName(); 138 } 139 140 // Check that the content type is an authorized content type. 141 Set<String> validCTypes = _cTypesAssignmentHandler.getAvailableContentTypes(page, zoneName, true); 142 for (String contentTypeId : content.getTypes()) 143 { 144 if (!validCTypes.contains(contentTypeId)) 145 { 146 ContentType cType = _cTypeEP.getExtension(contentTypeId); 147 result.put("error", true); 148 result.put("invalid-contenttype", cType.getLabel()); 149 return result; 150 } 151 } 152 153 ZoneItem zoneItem = null; 154 if (contentSiteName == null || page.getSiteName().equals(contentSiteName)) 155 { 156 // The content comes from the same site as the page: insert the content as a new zone item. 157 zoneItem = addContentReference(zone, content, metadataSetName); 158 } 159 else 160 { 161 // The content is from a different site: create a shared content in the zone. 162 if (!(content instanceof DefaultContent) || content instanceof SharedContent) 163 { 164 throw new IllegalArgumentException("The source content must be a DefaultContent but not a SharedContent."); 165 } 166 167 DefaultContent defaultContent = (DefaultContent) content; 168 169 if (!ArrayUtils.contains(defaultContent.getAllLabels(), WebConstants.LIVE_LABEL)) 170 { 171 result.put("error", true); 172 result.put("unpublished-content", defaultContent.getTitle()); 173 return result; 174 } 175 176 zoneItem = createSharedContent(zone, defaultContent, metadataSetName); 177 } 178 179 mPage.saveChanges(); 180 181 String zoneItemId = zoneItem.getId(); 182 result.put("zoneItemId", zoneItemId); 183 184 Map<String, Object> eventParams = new HashMap<>(); 185 eventParams.put(ObservationConstants.ARGS_PAGE, page); 186 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItemId); 187 eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, ZoneType.CONTENT); 188 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_CONTENT, zoneItem.getContent()); 189 _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_ADDED, _currentUserProvider.getUser(), eventParams)); 190 191 return result; 192 } 193 194 /** 195 * Add the given content as a zone item in the given zone. 196 * @param zone the zone to add the content in. 197 * @param content the content to add. 198 * @param metadataSetName the metadata set name. 199 * @return the added zone item 200 */ 201 protected ZoneItem addContentReference(ModifiableZone zone, Content content, String metadataSetName) 202 { 203 ModifiableZoneItem zoneItem = zone.addZoneItem(); 204 zoneItem.setType(ZoneType.CONTENT); 205 206 zoneItem.setContent(content); 207 zoneItem.setMetadataSetName(metadataSetName); 208 209 return zoneItem; 210 } 211 212 /** 213 * Create a shared content referencing the given content and add the shared one to the zone. 214 * @param zone the zone to create the shared content in. 215 * @param originalContent the original content. 216 * @param metadataSetName the metadata set name. 217 * @return the added zone item 218 */ 219 protected ZoneItem createSharedContent(ModifiableZone zone, DefaultContent originalContent, String metadataSetName) 220 { 221 ModifiableZoneItem zoneItem = zone.addZoneItem(); 222 zoneItem.setType(ZoneType.CONTENT); 223 224 DefaultSharedContent content = _sharedContentManager.createSharedContent(zone.getPage().getSite(), originalContent); 225 226 zoneItem.setContent(content); 227 zoneItem.setMetadataSetName(metadataSetName); 228 229 return zoneItem; 230 } 231 232 /** 233 * Remove a zone item from page 234 * @param zoneItemId The id of zone item to delete 235 */ 236 @Callable 237 public void removeZoneItem (String zoneItemId) 238 { 239 ZoneItem zoneItem = _resolver.resolveById(zoneItemId); 240 241 if (!(zoneItem instanceof ModifiableZoneItem)) 242 { 243 throw new IllegalArgumentException("Can not remove a non-modifiable zone item " + zoneItemId); 244 } 245 246 ModifiablePage page = (ModifiablePage) zoneItem.getZone().getPage(); 247 248 Map<String, Object> eventParams = new HashMap<>(); 249 eventParams.put(ObservationConstants.ARGS_PAGE, page); 250 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItemId); 251 eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, zoneItem.getType()); 252 if (zoneItem.getType() == ZoneType.CONTENT) 253 { 254 // In case of a zone item of type content, provide it. 255 // There should be no problem as the observers are processed synchronously and, 256 // if the user want it to be deleted, it will happen in a future client call. 257 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_CONTENT, zoneItem.getContent()); 258 } 259 260 ((ModifiableZoneItem) zoneItem).remove(); 261 page.saveChanges(); 262 263 _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_DELETED, _currentUserProvider.getUser(), eventParams)); 264 } 265 266 /** 267 * Get the unreferenced contents of a {@link Page} or a {@link ZoneItem} 268 * @param id The id of page or zone item 269 * @return The list of unreferenced contents 270 */ 271 @Callable 272 public Map<String, Object> getZoneItemElementInfo(String id) 273 { 274 ZoneItem zoneItem = _resolver.resolveById(id); 275 276 Map<String, Object> info = new HashMap<>(); 277 info.put("type", zoneItem.getType().toString().toLowerCase()); 278 279 Content content = getContent(zoneItem); 280 if (content != null) 281 { 282 info.put("id", content.getId()); 283 info.put("name", content.getName()); 284 info.put("title", content.getTitle()); 285 info.put("isNew", _isNew(content)); 286 info.put("isShared", content instanceof SharedContent); 287 info.put("hasShared", _sharedContentManager.hasSharedContents(content)); 288 info.put("isReferenced", _isReferenced(content)); 289 } 290 else 291 { 292 Service service = getService(zoneItem); 293 if (service != null) 294 { 295 info.put("id", service.getId()); 296 info.put("label", service.getLabel()); 297 info.put("url", service.getURL()); 298 } 299 } 300 301 return info; 302 } 303 304 /** 305 * Get the content of a zone item. Can be null if zone item is a service zone item. 306 * @param zoneItem The zone item 307 * @return The content or null if zone item is a service zone item. 308 */ 309 public Content getContent (ZoneItem zoneItem) 310 { 311 if (zoneItem.getType() == ZoneItem.ZoneType.CONTENT) 312 { 313 return zoneItem.getContent(); 314 } 315 return null; 316 } 317 318 private boolean _isReferenced (Content content) 319 { 320 return content instanceof WebContent && ((WebContent) content).getReferencingPages().size() > 1; 321 } 322 323 private boolean _isNew (Content content) 324 { 325 boolean isNew = false; 326 if (content instanceof WorkflowAwareContent) 327 { 328 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 329 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 330 331 long workflowId = waContent.getWorkflowId(); 332 isNew = workflow.getHistorySteps(workflowId).isEmpty(); 333 } 334 return isNew; 335 } 336 337 /** 338 * Get the service of a zone item. Can be null if zone item is not a service zone item. 339 * @param zoneItem The zone item 340 * @return The service or null if zone item is not a service zone item. 341 */ 342 public Service getService (ZoneItem zoneItem) 343 { 344 if (zoneItem.getType() == ZoneItem.ZoneType.SERVICE) 345 { 346 return _serviceEP.getExtension(zoneItem.getServiceId()); 347 } 348 return null; 349 } 350 351 /** 352 * Move a zone item of a page before/after another zone item of the same page 353 * @param zoneItemId The identifier of the zone item to move 354 * @param zoneName The destination zone name 355 * @param beforeOrAfter true if before or false after 356 * @param beforeOrAfterItemId The target zone item can be null 357 * @param pageId The concerned page id 358 * @return true when success 359 * @throws UnknownAmetysObjectException If an error occurred 360 * @throws AmetysRepositoryException If an error occurred 361 * @throws RepositoryException If an error occurred 362 */ 363 @Callable 364 public boolean moveZoneItemTo(String zoneItemId, String zoneName, boolean beforeOrAfter, String beforeOrAfterItemId, String pageId) throws UnknownAmetysObjectException, AmetysRepositoryException, RepositoryException 365 { 366 UserIdentity user = _currentUserProvider.getUser(); 367 368 ModifiablePage page = _resolver.resolveById(pageId); 369 370 Zone zone; 371 if (!page.hasZone(zoneName)) 372 { 373 zone = page.createZone(zoneName); 374 } 375 else 376 { 377 zone = page.getZone(zoneName); 378 } 379 380 ZoneItem zoneItem = page instanceof JCRAmetysObject ? (ZoneItem) _resolver.resolveById(zoneItemId, ((JCRAmetysObject) page).getNode().getSession()) : (ZoneItem) _resolver.resolveById(zoneItemId); 381 382 _checkZoneItem(page, zoneName, zoneItem, user); 383 384 _checkArguments(zoneItemId, zoneName, pageId, user, page, zone, zoneItem); 385 386 ((ModifiableZoneItem) zoneItem).moveTo(zone, false); 387 388 // For the moment the zoneItem is at the end 389 if (StringUtils.isNotBlank(beforeOrAfterItemId)) 390 { 391 if (beforeOrAfter) 392 { 393 ZoneItem futureFollowingZoneItem = _resolver.resolveById(beforeOrAfterItemId); 394 ((ModifiableZoneItem) zoneItem).orderBefore(futureFollowingZoneItem); 395 } 396 else 397 { 398 boolean isNext = false; 399 for (ZoneItem zi : zone.getZoneItems()) 400 { 401 if (isNext) 402 { 403 ((ModifiableZoneItem) zoneItem).orderBefore(zi); 404 break; 405 } 406 else if (beforeOrAfterItemId.equals(zi.getId())) 407 { 408 isNext = true; 409 } 410 } 411 } 412 } 413 414 ModifiablePage zoneItemPage = (ModifiablePage) zoneItem.getZone().getPage(); 415 if (zoneItemPage.needsSave()) 416 { 417 zoneItemPage.saveChanges(); 418 } 419 420 Map<String, Object> eventParams = new HashMap<>(); 421 eventParams.put(ObservationConstants.ARGS_PAGE, page); 422 eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItemId); 423 eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, zoneItem.getType()); 424 _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_MOVED, user, eventParams)); 425 426 return true; 427 } 428 429 /** 430 * Check if the zone item can be moved to the given zone of the given page. 431 * @param page the page. 432 * @param zoneName the zone name. 433 * @param zoneItem the zone item to move. 434 * @param user the user 435 */ 436 protected void _checkZoneItem(ModifiablePage page, String zoneName, ZoneItem zoneItem, UserIdentity user) 437 { 438 if (ZoneType.CONTENT.equals(zoneItem.getType())) 439 { 440 String[] contentTypes = zoneItem.getContent().getTypes(); 441 442 Set<String> availableCTypes = _cTypesAssignmentHandler.getAvailableContentTypes(page, zoneName, true); 443 444 for (String contentType : contentTypes) 445 { 446 if (!availableCTypes.contains(contentType)) 447 { 448 throw new IllegalArgumentException("The user '" + user + " illegally tried to move a content of type '" + contentType + "' to the zone '" + zoneName + "' of page '" + page.getId() + "'."); 449 } 450 } 451 } 452 else if (ZoneType.SERVICE.equals(zoneItem.getType())) 453 { 454 String serviceId = zoneItem.getServiceId(); 455 456 Set<String> services = _serviceAssignmentHandler.getAvailableServices(page, zoneName); 457 if (!services.contains(serviceId)) 458 { 459 throw new IllegalArgumentException("The user '" + user + " illegally tried to move a service of id '" + serviceId + "' to the zone '" + zoneName + "' of page '" + page.getId() + "'."); 460 } 461 } 462 } 463 464 private void _checkArguments(String zoneItemId, String zoneName, String pageId, UserIdentity user, ModifiablePage page, Zone zone, ZoneItem zoneItem) 465 { 466 if (_rightManager.hasRight(user, "Web_Rights_Page_OrganizeZoneItem", page) != RightResult.RIGHT_ALLOW) 467 { 468 throw new IllegalArgumentException("User '" + user + "' tryed without convinient privileges to move zone item '" + zoneItemId + "' to the zone '" + zoneName + "' of the page '" + pageId + "' !"); 469 } 470 else if (zoneItem == null) 471 { 472 throw new IllegalArgumentException("User '" + user + "' tryed to move unexisting zone item '" + zoneItemId + "' to the zone '" + zoneName + "' of the page '" + pageId + "' !"); 473 } 474 else if (zone == null) 475 { 476 throw new IllegalArgumentException("User '" + user + "' tryed to move zone item '" + zoneItemId + "' to an unexisting zone '" + zoneName + "' of the page '" + pageId + "' !"); 477 } 478 else if (!(zone instanceof ModifiableZone)) 479 { 480 throw new IllegalArgumentException("User '" + user + "' tryed to move zone item '" + zoneItemId + "' to the zone '" + zoneName + "' of the page '" + pageId + "' but this zone is not modifiable !"); 481 } 482 else if (!(zoneItem instanceof ModifiableZoneItem)) 483 { 484 throw new IllegalArgumentException("User '" + user + "' tryed to move zone item '" + zoneItemId + "' to the zone '" + zoneName + "' of the page '" + pageId + "' but this zone item is not modifiable !"); 485 } 486 } 487}