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