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.plugins.odfweb.clientside; 017 018import java.util.Arrays; 019import java.util.HashMap; 020import java.util.Map; 021import java.util.Set; 022 023import javax.jcr.Node; 024import javax.jcr.Property; 025import javax.jcr.RepositoryException; 026import javax.jcr.Value; 027 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.commons.lang3.StringUtils; 031import org.apache.commons.lang3.Strings; 032import org.apache.jackrabbit.value.StringValue; 033 034import org.ametys.core.observation.Event; 035import org.ametys.core.observation.ObservationManager; 036import org.ametys.core.ui.Callable; 037import org.ametys.core.ui.StaticClientSideElement; 038import org.ametys.core.util.LambdaUtils; 039import org.ametys.odf.catalog.Catalog; 040import org.ametys.odf.catalog.CatalogsManager; 041import org.ametys.plugins.odfweb.OdfWebObservationConstants; 042import org.ametys.plugins.odfweb.repository.AbstractOdfPage; 043import org.ametys.plugins.odfweb.repository.FirstLevelPageFactory; 044import org.ametys.plugins.odfweb.repository.OdfPageHandler; 045import org.ametys.plugins.repository.AmetysObjectResolver; 046import org.ametys.plugins.repository.jcr.JCRAmetysObject; 047import org.ametys.runtime.i18n.I18nizableText; 048import org.ametys.runtime.i18n.I18nizableTextParameter; 049import org.ametys.web.ObservationConstants; 050import org.ametys.web.repository.page.ModifiablePage; 051import org.ametys.web.repository.page.Page; 052 053import com.google.common.collect.ObjectArrays; 054 055/** 056 * This element creates an action button to set the ODF root page 057 */ 058public class ODFRootClientSideElement extends StaticClientSideElement 059{ 060 /** Ametys resolver */ 061 protected AmetysObjectResolver _resolver; 062 /** Catalog manager */ 063 protected CatalogsManager _catalogsManager; 064 /** Odf page handler */ 065 protected OdfPageHandler _odfPageHandler; 066 /** Observation manager */ 067 protected ObservationManager _observationManager; 068 069 @Override 070 public void service(ServiceManager smanager) throws ServiceException 071 { 072 super.service(smanager); 073 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 074 _catalogsManager = (CatalogsManager) smanager.lookup(CatalogsManager.ROLE); 075 _odfPageHandler = (OdfPageHandler) smanager.lookup(OdfPageHandler.ROLE); 076 _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); 077 } 078 079 /** 080 * Get odf root page status for the current page 081 * @param pageId the page identifier 082 * @return A map representing its status 083 */ 084 @Callable(rights = "ODF_Rights_SetRootPage") 085 public Map<String, Object> getStatus(String pageId) 086 { 087 Map<String, Object> results = new HashMap<>(); 088 Page page = _resolver.resolveById(pageId); 089 090 // page id 091 results.put("page-id", page.getId()); 092 093 if (page instanceof JCRAmetysObject) 094 { 095 if (_odfPageHandler.isODFRootPage(page)) 096 { 097 results.put("is-odf-root", true); 098 099 // Odf root page description 100 I18nizableText rawDescription = (I18nizableText) this._script.getParameters().get("odf-root-page-description"); 101 Map<String, I18nizableTextParameter> i18nParams = new HashMap<>(); 102 i18nParams.put("title", new I18nizableText(page.getTitle())); 103 104 I18nizableText description = new I18nizableText(rawDescription.getCatalogue(), rawDescription.getKey(), i18nParams); 105 results.put("odf-root-page-description", description); 106 107 // remove odf root 108 rawDescription = (I18nizableText) this._script.getParameters().get("remove-odf-page-description"); 109 description = new I18nizableText(rawDescription.getCatalogue(), rawDescription.getKey(), i18nParams); 110 results.put("remove-odf-page-description", description); 111 112 // Catalog Odf root page descrption 113 String catalogCode = page.getValue(OdfPageHandler.CATALOG_DATA_NAME); 114 if (StringUtils.isNotEmpty(catalogCode)) 115 { 116 Catalog catalog = _catalogsManager.getCatalog(catalogCode); 117 I18nizableText catalogText = new I18nizableText(catalog != null ? catalog.getTitle() : catalogCode); 118 119 i18nParams = new HashMap<>(); 120 i18nParams.put("catalog", catalogText); 121 122 rawDescription = (I18nizableText) this._script.getParameters().get("catalog-description"); 123 description = new I18nizableText(rawDescription.getCatalogue(), rawDescription.getKey(), i18nParams); 124 results.put("catalog-description", description); 125 } 126 } 127 else 128 { 129 // Add odf root page 130 results.put("add-odf-page", true); 131 132 I18nizableText rawDescription = (I18nizableText) this._script.getParameters().get("add-odf-page-description"); 133 Map<String, I18nizableTextParameter> i18nParams = new HashMap<>(); 134 i18nParams.put("title", new I18nizableText(page.getTitle())); 135 136 I18nizableText description = new I18nizableText(rawDescription.getCatalogue(), rawDescription.getKey(), i18nParams); 137 results.put("add-odf-page-description", description); 138 } 139 } 140 else 141 { 142 // Invalid page 143 results.put("invalid-page", true); 144 145 I18nizableText rawDescription = (I18nizableText) this._script.getParameters().get("no-jcr-page-description"); 146 Map<String, I18nizableTextParameter> i18nParams = new HashMap<>(); 147 i18nParams.put("title", new I18nizableText(page.getTitle())); 148 149 I18nizableText description = new I18nizableText(rawDescription.getCatalogue(), rawDescription.getKey(), i18nParams); 150 results.put("no-jcr-page-description", description); 151 } 152 153 return results; 154 } 155 156 /** 157 * Retrieves odf root page properties 158 * @param pageId page identifier 159 * @return the map of properties 160 */ 161 @Callable(rights = "ODF_Rights_SetRootPage") 162 public Map<String, Object> getOdfRootPageInfo(String pageId) 163 { 164 Map<String, Object> properties = new HashMap<>(); 165 166 Page page = _resolver.resolveById(pageId); 167 Set<Page> currentODFPages = _odfPageHandler.getOdfRootPages(page.getSiteName(), page.getSitemapName()); 168 169 if (_catalogsManager.getCatalogs().isEmpty()) 170 { 171 properties.put("noCatalog", true); 172 } 173 174 if (!currentODFPages.contains(page)) 175 { 176 properties.put("isOdfRootPage", false); 177 178 properties.put("areChildrenIndexable", true); 179 } 180 else 181 { 182 properties.put("isOdfRootPage", true); 183 184 properties.put("catalog", page.getValueOrDefault(OdfPageHandler.CATALOG_DATA_NAME, StringUtils.EMPTY)); 185 properties.put("firstLevel", page.getValueOrDefault(OdfPageHandler.LEVEL1_ATTRIBUTE_NAME, StringUtils.EMPTY)); 186 properties.put("secondLevel", page.getValueOrDefault(OdfPageHandler.LEVEL2_ATTRIBUTE_NAME, StringUtils.EMPTY)); 187 properties.put("areChildrenIndexable", page.getValueOrDefault(AbstractOdfPage.INDEXABLE_CHILDREN, true)); 188 } 189 190 return properties; 191 } 192 193 /** 194 * Add or update the odf root page property on a page 195 * @param pageId the page identifier 196 * @param catalog the catalog to set 197 * @param firstLevel the first level to set 198 * @param secondLevel the second level to set 199 * @param areChildrenIndexable set to <code>true</code> to index children pages 200 * @throws RepositoryException if a repository error occurs 201 * @return true if the page has actually been modified 202 */ 203 @Callable(rights = "ODF_Rights_SetRootPage") 204 public boolean addOrUpdateOdfRootProperty(String pageId, String catalog, String firstLevel, String secondLevel, boolean areChildrenIndexable) throws RepositoryException 205 { 206 Page page = _resolver.resolveById(pageId); 207 Set<Page> currentODFPages = _odfPageHandler.getOdfRootPages(page.getSiteName(), page.getSitemapName()); 208 209 if (!currentODFPages.contains(page)) 210 { 211 return _addOdfRootProperty(page, catalog, firstLevel, secondLevel, areChildrenIndexable); 212 } 213 else 214 { 215 return _updateOdfRootProperty(page, catalog, firstLevel, secondLevel, areChildrenIndexable); 216 } 217 } 218 219 private boolean _addOdfRootProperty(Page page, String catalog, String firstLevel, String secondLevel, boolean areChildrenIndexable) throws RepositoryException 220 { 221 boolean hasChanges = false; 222 223 if (page instanceof JCRAmetysObject) 224 { 225 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 226 227 // Add odf root property 228 Property virtualProperty = _getVirtualProperty(jcrPage); 229 Value[] oldValues = virtualProperty != null ? virtualProperty.getValues() : new Value[]{}; 230 231 boolean hasVirtualProperty = Arrays.stream(oldValues) 232 .anyMatch(LambdaUtils.wrapPredicate(ODFRootClientSideElement::isVirtualPageFactoryValue)); 233 234 if (!hasVirtualProperty) 235 { 236 Value[] newValues = ObjectArrays.concat(oldValues, new StringValue(FirstLevelPageFactory.class.getName())); 237 _setVirtualProperty(jcrPage, newValues); 238 } 239 240 hasChanges = _updateOdfRootProperty(page, catalog, firstLevel, secondLevel, areChildrenIndexable); 241 } 242 243 if (hasChanges) 244 { 245 _notifyOdfRootPageChange(page); 246 } 247 248 return hasChanges; 249 } 250 251 private boolean _updateOdfRootProperty(Page page, String catalog, String firstLevel, String secondLevel, boolean areChildrenIndexable) 252 { 253 boolean hasChanges = _setOdfRootAttributes(page, catalog, firstLevel, secondLevel, areChildrenIndexable); 254 255 if (hasChanges) 256 { 257 // Odf root updated observer 258 Map<String, Object> eventParams = new HashMap<>(); 259 eventParams.put(ObservationConstants.ARGS_PAGE, page); 260 _observationManager.notify(new Event(OdfWebObservationConstants.ODF_ROOT_UPDATED, _currentUserProvider.getUser(), eventParams)); 261 262 _notifyOdfRootPageChange(page); 263 } 264 265 return hasChanges; 266 } 267 268 private boolean _setOdfRootAttributes(Page page, String catalog, String firstLevel, String secondLevel, boolean areChildrenIndexable) 269 { 270 boolean hasChanges = false; 271 272 if (page instanceof ModifiablePage modifiablePage) 273 { 274 // Set the ODF catalog property 275 if (StringUtils.isNotEmpty(catalog)) 276 { 277 modifiablePage.setValue(OdfPageHandler.CATALOG_DATA_NAME, catalog); 278 } 279 280 // Set the level properties. 281 if (StringUtils.isNotEmpty(firstLevel)) 282 { 283 modifiablePage.setValue(OdfPageHandler.LEVEL1_ATTRIBUTE_NAME, firstLevel); 284 } 285 else 286 { 287 modifiablePage.removeValue(OdfPageHandler.LEVEL1_ATTRIBUTE_NAME); //TODO level 1 mandatory ?? 288 } 289 290 if (StringUtils.isNotEmpty(secondLevel)) 291 { 292 modifiablePage.setValue(OdfPageHandler.LEVEL2_ATTRIBUTE_NAME, secondLevel); 293 } 294 else 295 { 296 modifiablePage.removeValue(OdfPageHandler.LEVEL2_ATTRIBUTE_NAME); 297 } 298 299 modifiablePage.setValue(AbstractOdfPage.INDEXABLE_CHILDREN, areChildrenIndexable); 300 301 if (modifiablePage.needsSave()) 302 { 303 modifiablePage.saveChanges(); 304 hasChanges = true; 305 } 306 } 307 308 return hasChanges; 309 } 310 311 /** 312 * Remove the odf root page property on a page 313 * @param pageId the page identifier 314 * @return true if the property has actually been removed 315 * @throws RepositoryException if a repository error occurs 316 */ 317 @Callable(rights = "ODF_Rights_SetRootPage") 318 public boolean removeOdfRootPage(String pageId) throws RepositoryException 319 { 320 boolean hasChanges = false; 321 Page page = _resolver.resolveById(pageId); 322 323 if (page instanceof JCRAmetysObject) 324 { 325 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 326 Property virtualProperty = _getVirtualProperty(jcrPage); 327 328 if (virtualProperty != null) 329 { 330 Value[] oldValues = virtualProperty.getValues(); 331 332 // remove the virtual page property 333 Value[] newValues = Arrays.stream(oldValues) 334 .filter(LambdaUtils.wrapPredicate(ODFRootClientSideElement::isVirtualPageFactoryValue).negate()) 335 .toArray(Value[]::new); 336 337 _setVirtualProperty(jcrPage, newValues); 338 339 // Remove the level properties. 340 ModifiablePage modifiablePage = (ModifiablePage) page; 341 modifiablePage.removeValue(OdfPageHandler.LEVEL1_ATTRIBUTE_NAME); 342 modifiablePage.removeValue(OdfPageHandler.LEVEL2_ATTRIBUTE_NAME); 343 modifiablePage.removeValue(OdfPageHandler.CATALOG_DATA_NAME); 344 modifiablePage.removeValue(AbstractOdfPage.INDEXABLE_CHILDREN); 345 346 if (jcrPage.needsSave()) 347 { 348 jcrPage.saveChanges(); 349 hasChanges = true; 350 } 351 } 352 } 353 354 if (hasChanges) 355 { 356 // Odf root deleted observer 357 Map<String, Object> eventParams = new HashMap<>(); 358 eventParams.put(ObservationConstants.ARGS_PAGE, page); 359 _observationManager.notify(new Event(OdfWebObservationConstants.ODF_ROOT_DELETED, _currentUserProvider.getUser(), eventParams)); 360 361 _notifyOdfRootPageChange(page); 362 } 363 364 return hasChanges; 365 } 366 367 private void _notifyOdfRootPageChange(Page page) 368 { 369 // clear root cache 370 _odfPageHandler.clearRootCache(); 371 372 // Page changed observer 373 Map<String, Object> eventParams = new HashMap<>(); 374 eventParams.put(ObservationConstants.ARGS_PAGE, page); 375 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams)); 376 } 377 378 private Property _getVirtualProperty(JCRAmetysObject jcrPage) throws RepositoryException 379 { 380 Node node = jcrPage.getNode(); 381 382 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 383 { 384 return node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY); 385 } 386 387 return null; 388 } 389 390 private void _setVirtualProperty(JCRAmetysObject jcrPage, Value[] values) throws RepositoryException 391 { 392 jcrPage.getNode().setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values); 393 } 394 395 /** 396 * Tests if a JCR Value represents the virtual page property 397 * @param value the value to test 398 * @return true if it is the case 399 * @throws RepositoryException if a repository error occurs 400 */ 401 private static boolean isVirtualPageFactoryValue(Value value) throws RepositoryException 402 { 403 return Strings.CS.equals(value.getString(), FirstLevelPageFactory.class.getName()); 404 } 405}