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