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