001/* 002 * Copyright 2017 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.workspaces; 017 018import java.util.HashMap; 019import java.util.Map; 020 021import javax.jcr.Node; 022import javax.jcr.NodeIterator; 023import javax.jcr.RepositoryException; 024 025import org.apache.avalon.framework.context.Context; 026import org.apache.avalon.framework.context.ContextException; 027import org.apache.avalon.framework.context.Contextualizable; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.commons.lang.StringUtils; 032 033import org.ametys.core.observation.Event; 034import org.ametys.core.observation.ObservationManager; 035import org.ametys.core.right.RightManager; 036import org.ametys.core.user.CurrentUserProvider; 037import org.ametys.core.user.UserManager; 038import org.ametys.core.util.I18nUtils; 039import org.ametys.plugins.core.user.UserHelper; 040import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 041import org.ametys.plugins.repository.AmetysObject; 042import org.ametys.plugins.repository.AmetysObjectIterable; 043import org.ametys.plugins.repository.AmetysObjectResolver; 044import org.ametys.plugins.repository.AmetysRepositoryException; 045import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 046import org.ametys.plugins.repository.events.JCREventHelper; 047import org.ametys.plugins.workspaces.project.ProjectConstants; 048import org.ametys.plugins.workspaces.project.ProjectManager; 049import org.ametys.plugins.workspaces.project.modules.WorkspaceModule; 050import org.ametys.plugins.workspaces.project.objects.Project; 051import org.ametys.plugins.workspaces.project.rights.ProjectRightHelper; 052import org.ametys.runtime.i18n.I18nizableText; 053import org.ametys.runtime.model.ElementDefinition; 054import org.ametys.runtime.plugin.component.AbstractLogEnabled; 055import org.ametys.runtime.plugin.component.PluginAware; 056import org.ametys.web.ObservationConstants; 057import org.ametys.web.repository.page.ModifiablePage; 058import org.ametys.web.repository.page.Page; 059import org.ametys.web.repository.page.Page.PageType; 060import org.ametys.web.repository.page.PageDAO; 061import org.ametys.web.repository.site.Site; 062import org.ametys.web.repository.sitemap.Sitemap; 063import org.ametys.web.service.Service; 064import org.ametys.web.service.ServiceExtensionPoint; 065import org.ametys.web.skin.Skin; 066import org.ametys.web.skin.SkinTemplate; 067import org.ametys.web.skin.SkinsManager; 068 069/** 070 * Abstract class for {@link WorkspaceModule} implementation 071 * 072 */ 073public abstract class AbstractWorkspaceModule extends AbstractLogEnabled implements WorkspaceModule, Serviceable, Contextualizable, PluginAware 074{ 075 /** Project manager */ 076 protected ProjectManager _projectManager; 077 /** Project right helper */ 078 protected ProjectRightHelper _projectRightHelper; 079 /** User manager */ 080 protected UserManager _userManager; 081 /** Ametys resolver */ 082 protected AmetysObjectResolver _resolver; 083 /** The rights manager */ 084 protected RightManager _rightManager; 085 /** Observer manager. */ 086 protected ObservationManager _observationManager; 087 /** The current user provider. */ 088 protected CurrentUserProvider _currentUserProvider; 089 /** The users manager */ 090 protected UserHelper _userHelper; 091 /** The i18n utils. */ 092 protected I18nUtils _i18nUtils; 093 /** The skins manager. */ 094 protected SkinsManager _skinsManager; 095 /** The page DAO */ 096 protected PageDAO _pageDAO; 097 /** The avalon context */ 098 protected Context _context; 099 /** The plugin name */ 100 protected String _pluginName; 101 /** The services handler */ 102 protected ServiceExtensionPoint _serviceEP; 103 104 @Override 105 public void service(ServiceManager manager) throws ServiceException 106 { 107 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 108 _projectRightHelper = (ProjectRightHelper) manager.lookup(ProjectRightHelper.ROLE); 109 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 110 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 111 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 112 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 113 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 114 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 115 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 116 _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE); 117 _pageDAO = (PageDAO) manager.lookup(PageDAO.ROLE); 118 _serviceEP = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE); 119 } 120 121 @Override 122 public void contextualize(Context context) throws ContextException 123 { 124 _context = context; 125 } 126 127 public void setPluginInfo(String pluginName, String featureName, String id) 128 { 129 _pluginName = pluginName; 130 } 131 132 @Override 133 public void deactivateModule(Project project) 134 { 135 _deletePages(project, getModulePageName()); 136 _internalDeactivateModule(project); 137 138 // Delete root 139 ModifiableResourceCollection moduleRoot = getModuleRoot(project, false); 140 if (moduleRoot != null) 141 { 142 moduleRoot.remove(); 143 } 144 145 // Delete events 146 try 147 { 148 NodeIterator events = JCREventHelper.getEvents(project, getAllowedEventTypes().toArray(new String[]{})); 149 while (events.hasNext()) 150 { 151 Node event = (Node) events.next(); 152 event.remove(); 153 } 154 } 155 catch (RepositoryException e) 156 { 157 getLogger().warn("Unable to delete project '" + project.getName() + "' events for module '" + this.getId() + "'", e); 158 } 159 } 160 161 @Override 162 public void activateModule(Project project) 163 { 164 for (Site site : project.getSites()) 165 { 166 for (Sitemap sitemap : site.getSitemaps()) 167 { 168 initializeSitemap(sitemap); 169 } 170 } 171 172 _internalActivateModule(project); 173 174 // create the resources root node 175 getModuleRoot(project, true); 176 } 177 178 @Override 179 public void initializeSitemap(Sitemap sitemap) 180 { 181 ModifiablePage page = _createModulePage(sitemap, getModulePageName(), getModulePageTitle(), getModulePageTemplate()); 182 183 if (page != null) 184 { 185 page.tag("SECTION"); 186 page.tag(getModuleTagName()); 187 188 initializeModulePage(page); 189 190 page.saveChanges(); 191 192 Map<String, Object> eventParams = new HashMap<>(); 193 eventParams.put(ObservationConstants.ARGS_PAGE, page); 194 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams)); 195 } 196 } 197 198 @Override 199 public AmetysObjectIterable<Page> getModulePages(Project project, String language) 200 { 201 return _projectManager.getProjectPages(project, getModuleTagName(), language); 202 } 203 204 205 /** 206 * Create a new page if not already exists 207 * @param sitemap The sitemap where the page will be created 208 * @param name The page's name 209 * @param pageTitle The page's title as i18nizable text 210 * @param skinTemplate The template from the skin to apply on the page 211 * @return the created page or <code>null</code> if page already exists 212 */ 213 protected ModifiablePage _createModulePage(Sitemap sitemap, String name, I18nizableText pageTitle, String skinTemplate) 214 { 215 if (!sitemap.hasChild(name)) 216 { 217 ModifiablePage page = sitemap.createChild(name, "ametys:defaultPage"); 218 219 // Title should not be missing, but just in case if the i18n message or the whole catalog does not exists in the requested language 220 // to prevent a non-user-friendly error and still generate the project workspace. 221 page.setTitle(StringUtils.defaultIfEmpty(_i18nUtils.translate(pageTitle, sitemap.getName()), "Missing title")); 222 page.setType(PageType.NODE); 223 page.setSiteName(sitemap.getSiteName()); 224 page.setSitemapName(sitemap.getName()); 225 226 Site site = page.getSite(); 227 Skin skin = _skinsManager.getSkin(site.getSkinId()); 228 SkinTemplate template = skin.getTemplate(skinTemplate); 229 if (template != null) 230 { 231 // Set the type and template. 232 page.setType(PageType.CONTAINER); 233 page.setTemplate(skinTemplate); 234 } 235 else 236 { 237 getLogger().error(String.format( 238 "The project workspace '%s' was created with the skin '%s' which doesn't possess the mandatory template '%s'.\nThe '%s' page of the project workspace could not be initialized.", 239 site.getName(), site.getSkinId(), skinTemplate, page.getName())); 240 } 241 242 sitemap.saveChanges(); 243 244 return page; 245 } 246 else 247 { 248 return null; 249 } 250 } 251 252 /** 253 * Delete all pages of the project sites matching the page name 254 * @param project The project 255 * @param pageName The page name 256 */ 257 protected void _deletePages(Project project, String pageName) 258 { 259 for (Site site : project.getSites()) 260 { 261 for (Sitemap sitemap : site.getSitemaps()) 262 { 263 if (sitemap.hasChild(pageName)) 264 { 265 ModifiablePage page = sitemap.getChild(pageName); 266 _pageDAO.deletePage(page, true); 267 } 268 } 269 270 site.saveChanges(); 271 } 272 } 273 274 /** 275 * Get the default value of the XSLT parameter of the given service. 276 * @param serviceId the service ID. 277 * @return the default XSLT parameter value. 278 */ 279 protected String _getDefaultXslt(String serviceId) 280 { 281 Service service = _serviceEP.hasExtension(serviceId) ? _serviceEP.getExtension(serviceId) : null; 282 if (service != null) 283 { 284 @SuppressWarnings("unchecked") 285 ElementDefinition<String> xsltParameterDefinition = (ElementDefinition<String>) service.getParameters().get("xslt"); 286 287 if (xsltParameterDefinition != null) 288 { 289 return xsltParameterDefinition.getDefaultValue(); 290 } 291 } 292 293 return StringUtils.EMPTY; 294 } 295 296 /** 297 * Returns the tag of apply to module page 298 * @return The tag for module page 299 */ 300 protected abstract String getModuleTagName(); 301 302 /** 303 * Returns the module page's name 304 * @return The module page's name 305 */ 306 protected abstract String getModulePageName(); 307 308 /** 309 * Returns the module page's title as i18n 310 * @return The module page's title 311 */ 312 protected abstract I18nizableText getModulePageTitle(); 313 314 /** 315 * Returns the template to use for module's page 316 * @return The template 317 */ 318 protected String getModulePageTemplate() 319 { 320 return ProjectConstants.PROJECT_TEMPLATE; 321 } 322 323 /** 324 * Initialize the module page 325 * @param modulePage The module page 326 */ 327 protected abstract void initializeModulePage(ModifiablePage modulePage); 328 329 /** 330 * Internal process when module is deactivated 331 * @param project The project 332 */ 333 protected void _internalDeactivateModule(Project project) 334 { 335 // Empty 336 } 337 338 /** 339 * Internal process when module is activated 340 * @param project The project 341 */ 342 protected void _internalActivateModule(Project project) 343 { 344 // Empty 345 } 346 347 /** 348 * Utility method to get or create an ametys object 349 * @param <A> A sub class of AmetysObject 350 * @param parent The parent object 351 * @param name The ametys object name 352 * @param type The ametys object type 353 * @param create True to create the object if it does not exist 354 * @return ametys object 355 * @throws AmetysRepositoryException if an repository error occurs 356 */ 357 protected <A extends AmetysObject> A _getAmetysObject(ModifiableTraversableAmetysObject parent, String name, String type, boolean create) throws AmetysRepositoryException 358 { 359 A object = null; 360 361 if (parent.hasChild(name)) 362 { 363 object = parent.getChild(name); 364 } 365 else if (create) 366 { 367 object = parent.createChild(name, type); 368 parent.saveChanges(); 369 } 370 371 return object; 372 } 373}