001/* 002 * Copyright 2010 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.jcr; 017 018import java.util.HashMap; 019import java.util.List; 020import java.util.Map; 021import java.util.Optional; 022 023import javax.jcr.Node; 024import javax.jcr.RepositoryException; 025 026import org.apache.commons.collections4.MapUtils; 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029import org.xml.sax.ContentHandler; 030import org.xml.sax.SAXException; 031 032import org.ametys.cms.data.holder.impl.DefaultModifiableModelAwareDataHolder; 033import org.ametys.cms.repository.Content; 034import org.ametys.plugins.repository.AmetysObject; 035import org.ametys.plugins.repository.AmetysRepositoryException; 036import org.ametys.plugins.repository.CopiableAmetysObject; 037import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 038import org.ametys.plugins.repository.RepositoryConstants; 039import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 040import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder; 041import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder; 042import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder; 043import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 044import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 045import org.ametys.plugins.repository.jcr.JCRAmetysObject; 046import org.ametys.plugins.repository.jcr.SimpleAmetysObject; 047import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata; 048import org.ametys.runtime.model.View; 049import org.ametys.runtime.model.exception.NotUniqueTypeException; 050import org.ametys.runtime.model.exception.UnknownTypeException; 051import org.ametys.runtime.model.type.DataContext; 052import org.ametys.web.parameters.view.ViewParametersManager; 053import org.ametys.web.parameters.view.ViewParametersModel; 054import org.ametys.web.repository.content.SharedContent; 055import org.ametys.web.repository.content.WebContent; 056import org.ametys.web.repository.content.jcr.DefaultWebContent; 057import org.ametys.web.repository.page.ModifiableZone; 058import org.ametys.web.repository.page.ModifiableZoneItem; 059import org.ametys.web.repository.page.SitemapElement; 060import org.ametys.web.repository.page.Zone; 061import org.ametys.web.service.Service; 062 063import com.opensymphony.workflow.WorkflowException; 064 065/** 066 * implementation of a {@link ModifiableZoneItem}. 067 */ 068public class DefaultZoneItem extends SimpleAmetysObject<DefaultZoneItemFactory> implements ModifiableZoneItem, CopiableAmetysObject 069{ 070 /** Constant for title metadata. */ 071 public static final String METADATA_TYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":type"; 072 /** Constant for service metadata. */ 073 public static final String METADATA_SERVICE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":service"; 074 /** Constant for content metadata. */ 075 public static final String METADATA_CONTENT = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":content"; 076 /** Constant service parameters node type. */ 077 public static final String SERVICE_PARAM_NODETYPE = RepositoryConstants.NAMESPACE_PREFIX + ":compositeMetadata"; 078 079 /** 080 * Constant for "metadata set name" metadata. 081 * @deprecated Use {@link #METADATA_VIEW_NAME} instead 082 */ 083 @Deprecated 084 public static final String METADATA_METADATASET_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":metadataSetName"; 085 086 /** 087 * Constant for view name metadata. 088 * TODO NEWATTRIBUTEAPI_SERVICE: Change the name of this metadata to viewName (there may be a lot of impacts..) 089 */ 090 public static final String METADATA_VIEW_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":metadataSetName"; 091 092 private static final Logger __LOGGER = LoggerFactory.getLogger(DefaultZoneItem.class); 093 094 /** 095 * Creates a {@link DefaultZoneItem}. 096 * 097 * @param node the node backing this {@link AmetysObject}. 098 * @param parentPath the parent path in the Ametys hierarchy. 099 * @param factory the {@link DefaultZoneItemFactory} which creates the AmetysObject. 100 */ 101 public DefaultZoneItem(Node node, String parentPath, DefaultZoneItemFactory factory) 102 { 103 super(node, parentPath, factory); 104 } 105 106 public ZoneType getType() throws AmetysRepositoryException 107 { 108 try 109 { 110 return ZoneType.valueOf(getNode().getProperty(METADATA_TYPE).getString()); 111 } 112 catch (RepositoryException e) 113 { 114 throw new AmetysRepositoryException("Unable to get type property", e); 115 } 116 } 117 118 public void setType(ZoneType type) throws AmetysRepositoryException 119 { 120 try 121 { 122 getNode().setProperty(METADATA_TYPE, type.name()); 123 } 124 catch (RepositoryException e) 125 { 126 throw new AmetysRepositoryException("Unable to set type property", e); 127 } 128 } 129 130 public <C extends Content> C getContent() throws AmetysRepositoryException 131 { 132 try 133 { 134 return _getFactory().<C>resolveAmetysObject(getNode().getProperty(METADATA_CONTENT).getNode()); 135 } 136 catch (RepositoryException e) 137 { 138 throw new AmetysRepositoryException("Unable to get content property", e); 139 } 140 } 141 142 public <C extends Content> void setContent(C content) throws AmetysRepositoryException 143 { 144 try 145 { 146 // FIXME stop using getNode that is JCR but use AmetysObject API... if it seems cool to you 147 getNode().setProperty(METADATA_CONTENT, ((JCRAmetysObject) content).getNode()); 148 } 149 catch (RepositoryException e) 150 { 151 throw new AmetysRepositoryException("Unable to set content property", e); 152 } 153 } 154 155 public String getViewName() throws AmetysRepositoryException 156 { 157 try 158 { 159 if (getNode().hasProperty(METADATA_VIEW_NAME)) 160 { 161 return getNode().getProperty(METADATA_VIEW_NAME).getString(); 162 } 163 return null; 164 } 165 catch (RepositoryException e) 166 { 167 throw new AmetysRepositoryException("Unable to get view name property.", e); 168 } 169 } 170 171 @Override 172 public void setViewName(String viewName) throws AmetysRepositoryException 173 { 174 try 175 { 176 getNode().setProperty(METADATA_VIEW_NAME, viewName); 177 } 178 catch (RepositoryException e) 179 { 180 throw new AmetysRepositoryException("Unable to set view name property.", e); 181 } 182 } 183 184 @Override 185 public String getServiceId() throws AmetysRepositoryException 186 { 187 try 188 { 189 return getNode().getProperty(METADATA_SERVICE).getString(); 190 } 191 catch (RepositoryException e) 192 { 193 throw new AmetysRepositoryException("Unable to get service property", e); 194 } 195 } 196 197 @Override 198 public void setServiceId(String serviceId) throws AmetysRepositoryException 199 { 200 try 201 { 202 getNode().setProperty(METADATA_SERVICE, serviceId); 203 } 204 catch (RepositoryException e) 205 { 206 throw new AmetysRepositoryException("Unable to set service property", e); 207 } 208 } 209 210 /** 211 * {@inheritDoc} 212 * @throws IllegalStateException if the service referenced by this zone item does not exist 213 */ 214 public ModifiableModelAwareDataHolder getServiceParameters() throws AmetysRepositoryException, IllegalStateException 215 { 216 String serviceId = getServiceId(); 217 Service service = _getFactory().getService(serviceId); 218 if (service != null) 219 { 220 ModifiableRepositoryData repositoryData = _getServiceParametersRepositoryData(); 221 return new DefaultModifiableModelAwareDataHolder(repositoryData, service); 222 } 223 else 224 { 225 throw new IllegalStateException("The service '" + serviceId + "' referenced by the zone item '" + getId() + "' (" + getPath() + ") does not exist"); 226 } 227 } 228 229 /** 230 * Retrieves the {@link ModifiableRepositoryData} containing the service parameters of this zone item 231 * @return the {@link ModifiableRepositoryData} containing the service parameters of this zone item 232 */ 233 protected ModifiableRepositoryData _getServiceParametersRepositoryData() 234 { 235 JCRRepositoryData zoneItemRepositoryData = new JCRRepositoryData(getNode()); 236 return zoneItemRepositoryData.hasValue(SERVICE_PARAMETERS_DATA_NAME) ? zoneItemRepositoryData.getRepositoryData(SERVICE_PARAMETERS_DATA_NAME) : zoneItemRepositoryData.addRepositoryData(SERVICE_PARAMETERS_DATA_NAME, SERVICE_PARAM_NODETYPE); 237 } 238 239 public ModifiableModelAwareDataHolder getZoneItemParametersHolder() throws AmetysRepositoryException 240 { 241 JCRRepositoryData zoneItemRepositoryData = new JCRRepositoryData(getNode()); 242 243 ViewParametersManager viewParametersManager = _getFactory().getViewParametersManager(); 244 Optional<ViewParametersModel> zoneItemViewParameters = viewParametersManager.getZoneItemViewParametersModel(this); 245 if (zoneItemViewParameters.isEmpty()) 246 { 247 zoneItemViewParameters = Optional.of(new ViewParametersModel("zoneitem-no-param", new View(), MapUtils.EMPTY_SORTED_MAP)); 248 } 249 return viewParametersManager.getParametersHolder(zoneItemRepositoryData, zoneItemViewParameters.get()); 250 } 251 252 public ModifiableModelLessDataHolder getDataHolder() 253 { 254 ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode()); 255 return new DefaultModifiableModelLessDataHolder(_getFactory().getPageDataTypeExtensionPoint(), repositoryData); 256 } 257 258 public void dataToSAX(ContentHandler contentHandler, DataContext context) throws SAXException, UnknownTypeException, NotUniqueTypeException 259 { 260 for (String dataName : getDataNames()) 261 { 262 DataContext newContext = context.cloneContext().addSegmentToDataPath(dataName); 263 dataToSAX(contentHandler, dataName, newContext); 264 } 265 } 266 267 public void dataToSAX(ContentHandler contentHandler, String dataPath, DataContext context) throws SAXException 268 { 269 // Do not generate SAX events for service and view parameters 270 if (!_isViewOrServiceParameterRoot(dataPath)) 271 { 272 ModifiableZoneItem.super.dataToSAX(contentHandler, dataPath, context); 273 } 274 } 275 276 public Map<String, Object> dataToJSON(DataContext context) throws UnknownTypeException, NotUniqueTypeException 277 { 278 Map<String, Object> result = new HashMap<>(); 279 for (String dataName : getDataNames()) 280 { 281 DataContext newContext = context.cloneContext().addSegmentToDataPath(dataName); 282 result.put(dataName, dataToJSON(dataName, newContext)); 283 } 284 285 return result; 286 } 287 288 public Object dataToJSON(String dataPath, DataContext context) 289 { 290 // Do not convert service and view parameters 291 return !_isViewOrServiceParameterRoot(dataPath) 292 ? ModifiableZoneItem.super.dataToJSON(dataPath, context) 293 : null; 294 } 295 296 private boolean _isViewOrServiceParameterRoot(String dataPath) 297 { 298 return SERVICE_PARAMETERS_DATA_NAME.equals(dataPath) 299 || ViewParametersManager.CONTENT_VIEW_PARAMETERS_COMPOSITE_NAME.equals(dataPath) 300 || ViewParametersManager.SERVICE_VIEW_PARAMETERS_COMPOSITE_NAME.equals(dataPath) 301 || ViewParametersManager.VIEW_PARAMETERS_COMPOSITE_NAME.equals(dataPath); 302 } 303 304 @Override 305 public ModifiableCompositeMetadata getMetadataHolder() 306 { 307 __LOGGER.warn("The getMetadataHolder method of DefaultZoneItem is deprecated. Use the getDataHolder instead. StackTrace: ", new Exception()); 308 return super.getMetadataHolder(); 309 } 310 311 @Override 312 public ModifiableZoneItem copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException 313 { 314 return copyTo(parent, name); 315 } 316 317 @Override 318 public ModifiableZoneItem copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException 319 { 320 if (parent instanceof DefaultZone) 321 { 322 ModifiableZone parentZone = (ModifiableZone) parent; 323 ModifiableZoneItem cZoneItem = parentZone.addZoneItem(); 324 325 ZoneType type = getType(); 326 cZoneItem.setType(type); 327 328 // Copy view parameters 329 getZoneItemParametersHolder().copyTo(cZoneItem.getZoneItemParametersHolder()); 330 331 ViewParametersManager viewParametersManager = _getFactory().getViewParametersManager(); 332 if (ZoneType.SERVICE.equals(type)) 333 { 334 cZoneItem.setServiceId(getServiceId()); 335 getServiceParameters().copyTo(cZoneItem.getServiceParameters()); 336 337 // Copy view parameters 338 viewParametersManager.copyServiceViewParameters(this, cZoneItem); 339 } 340 else 341 { 342 Content content = getContent(); 343 if (content instanceof WebContent) 344 { 345 WebContent webContent = (WebContent) content; 346 if (getZone().getSitemapElement().getSiteName().equals(webContent.getSiteName())) 347 { 348 if (webContent instanceof SharedContent) 349 { 350 // No need to copy a shared content, set the reference to the shared content 351 cZoneItem.setContent(webContent); 352 cZoneItem.setViewName(getViewName()); 353 } 354 else if (webContent instanceof DefaultWebContent modifiableContent) 355 { 356 Content cContent = modifiableContent.copyTo(webContent.getSite().getRootContents(), null, 0, false); 357 358 // Update references to AmetysObject in RichText 359 _getFactory().getCopySiteComponent().updateLinksInRichText(getZone().getSitemapElement(), cZoneItem.getZone().getSitemapElement(), content, cContent); 360 361 // need to save before event notification 362 modifiableContent.saveChanges(); 363 modifiableContent.checkpoint(); 364 365 // link to copied ZoneItem 366 cZoneItem.setContent(cContent); 367 cZoneItem.saveChanges(); 368 369 try 370 { 371 _getFactory().getContentDAO().notifyContentCopied(cContent, false); 372 } 373 catch (WorkflowException e) 374 { 375 throw new AmetysRepositoryException(e); 376 } 377 } 378 } 379 else 380 { 381 // Copy reference 382 cZoneItem.setContent(getContent()); 383 } 384 } 385 else 386 { 387 // Copy reference 388 cZoneItem.setContent(getContent()); 389 } 390 391 String viewName = getViewName(); 392 if (viewName != null) 393 { 394 cZoneItem.setViewName(viewName); 395 } 396 397 // Copy view parameters 398 viewParametersManager.copyContentViewParameters(this, cZoneItem); 399 } 400 401 return cZoneItem; 402 } 403 else 404 { 405 throw new AmetysRepositoryException("Can not copy a zone item " + getId() + " of type DefaultZoneItem to something that is not a DefaultZone " + parent.getId()); 406 } 407 } 408 409 @Override 410 public boolean canMoveTo(AmetysObject newParent) throws AmetysRepositoryException 411 { 412 return newParent instanceof DefaultZone; 413 } 414 415 @Override 416 public void moveTo(AmetysObject newParent, boolean renameIfExist) throws AmetysRepositoryException, RepositoryIntegrityViolationException 417 { 418 if (!canMoveTo(newParent)) 419 { 420 throw new AmetysRepositoryException("Can not move a zone item " + getId() + " of type DefaultZoneItem to something that is not a DefaultZone " + newParent.getId()); 421 } 422 423 Node node = getNode(); 424 425 try 426 { 427 DefaultZone newParentZone = (DefaultZone) newParent; 428 429 // Move node 430 node.getSession().move(node.getPath(), newParentZone.getNode().getPath() + "/" + DefaultZone.ZONEITEMS_NODE_NAME + "/" + node.getName()); 431 } 432 catch (RepositoryException e) 433 { 434 throw new AmetysRepositoryException("Can not move DefaultZoneItem " + getId() + " to DefaultZone " + newParent.getId(), e); 435 } 436 } 437 438 @Override 439 public void orderBefore(AmetysObject siblingObject) throws AmetysRepositoryException 440 { 441 if (siblingObject != null && !(siblingObject instanceof JCRAmetysObject)) 442 { 443 throw new AmetysRepositoryException(String.format("Unable to order zone item '%s' before sibling '%s' (sibling is not a JCRAmetysObject)", this.getId(), siblingObject.getId())); 444 } 445 446 Node node = getNode(); 447 Node siblingNode = siblingObject != null ? ((JCRAmetysObject) siblingObject).getNode() : null; 448 449 try 450 { 451 node.getParent().orderBefore(node.getName() + "[" + node.getIndex() + "]", siblingNode != null ? siblingNode.getName() + "[" + siblingNode.getIndex() + "]" : null); 452 } 453 catch (RepositoryException e) 454 { 455 throw new AmetysRepositoryException(String.format("Unable to order zone item '%s' before sibling '%s'", this.getId(), siblingObject != null ? siblingObject.getId() : ""), e); 456 } 457 } 458 459 @Override 460 public Zone getZone() 461 { 462 return getParent().getParent(); 463 } 464 465 public ModifiableModelAwareDataHolder getContentViewParametersHolder(String contentViewName) throws AmetysRepositoryException 466 { 467 if (getType() != ZoneType.CONTENT) 468 { 469 throw new AmetysRepositoryException("Can't get content view parameters from a not content zone item"); 470 } 471 472 JCRRepositoryData zoneItemRepositoryData = new JCRRepositoryData(getNode()); 473 474 Content content = getContent(); 475 476 // Get the skin Id 477 SitemapElement sitemapElement = getZone().getSitemapElement(); 478 String skinId = sitemapElement.getSite().getSkinId(); 479 480 ViewParametersManager viewParametersManager = _getFactory().getViewParametersManager(); 481 Optional<ViewParametersModel> contentViewParameters = viewParametersManager.getContentViewParametersModel(skinId, content, contentViewName); 482 if (contentViewParameters.isEmpty()) 483 { 484 contentViewParameters = Optional.of(new ViewParametersModel("content-no-param", new View(), MapUtils.EMPTY_SORTED_MAP)); 485 } 486 487 return viewParametersManager.getContentViewParametersHolder(zoneItemRepositoryData, contentViewName, contentViewParameters.get()); 488 } 489 490 public ModifiableModelAwareDataHolder getServiceViewParametersHolder(String serviceViewName) throws AmetysRepositoryException 491 { 492 if (getType() != ZoneType.SERVICE) 493 { 494 throw new AmetysRepositoryException("Can't get service view parameters from a not service zone item"); 495 } 496 497 JCRRepositoryData zoneItemRepositoryData = new JCRRepositoryData(getNode()); 498 499 String serviceId = getServiceId(); 500 501 // Get the skin Id 502 SitemapElement sitemapElement = getZone().getSitemapElement(); 503 String skinId = sitemapElement.getSite().getSkinId(); 504 505 ViewParametersManager viewParametersManager = _getFactory().getViewParametersManager(); 506 Optional<ViewParametersModel> serviceViewParameters = viewParametersManager.getServiceViewParametersModel(skinId, serviceId, serviceViewName); 507 if (serviceViewParameters.isEmpty()) 508 { 509 serviceViewParameters = Optional.of(new ViewParametersModel("service-no-param", new View(), MapUtils.EMPTY_SORTED_MAP)); 510 } 511 512 return viewParametersManager.getServiceViewParametersHolder(zoneItemRepositoryData, serviceViewName, serviceViewParameters.get()); 513 } 514}