001/* 002 * Copyright 2016 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.project.observers; 017 018import java.io.File; 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.avalon.framework.configuration.Configuration; 027import org.apache.avalon.framework.configuration.ConfigurationException; 028import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 029import org.apache.avalon.framework.context.Context; 030import org.apache.avalon.framework.context.ContextException; 031import org.apache.avalon.framework.context.Contextualizable; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035import org.apache.cocoon.Constants; 036import org.apache.commons.lang.StringUtils; 037import org.xml.sax.SAXException; 038 039import org.ametys.core.model.type.AbstractStringElementType; 040import org.ametys.core.observation.Event; 041import org.ametys.core.observation.ObservationManager; 042import org.ametys.core.observation.Observer; 043import org.ametys.core.user.CurrentUserProvider; 044import org.ametys.core.util.I18nUtils; 045import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder; 046import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeaterEntry; 047import org.ametys.plugins.repository.model.RepeaterDefinition; 048import org.ametys.plugins.workflow.support.WorkflowProvider; 049import org.ametys.plugins.workspaces.project.ProjectManager; 050import org.ametys.plugins.workspaces.project.objects.Project; 051import org.ametys.runtime.i18n.I18nizableText; 052import org.ametys.runtime.model.ElementDefinition; 053import org.ametys.runtime.model.ModelItem; 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.ModifiableZone; 059import org.ametys.web.repository.page.ModifiableZoneItem; 060import org.ametys.web.repository.page.Page.PageType; 061import org.ametys.web.repository.page.ZoneItem.ZoneType; 062import org.ametys.web.repository.site.Site; 063import org.ametys.web.repository.sitemap.Sitemap; 064import org.ametys.web.service.Service; 065import org.ametys.web.service.ServiceExtensionPoint; 066import org.ametys.web.service.ServiceParameter; 067import org.ametys.web.skin.Skin; 068import org.ametys.web.skin.SkinTemplate; 069import org.ametys.web.skin.SkinTemplateZone; 070import org.ametys.web.skin.SkinsManager; 071 072/** 073 * Initializes all project workspace pages when a sitemap is created. 074 */ 075public class InitializeProjectSitemapObserver extends AbstractLogEnabled implements Observer, Serviceable, PluginAware, Contextualizable 076{ 077 /** Workspaces project manager */ 078 protected ProjectManager _projectManager; 079 080 /** The i18n utils. */ 081 protected I18nUtils _i18nUtils; 082 083 /** The observation manager. */ 084 protected ObservationManager _observationManager; 085 086 /** The skins manager. */ 087 protected SkinsManager _skinsManager; 088 089 /** Current user provider */ 090 protected CurrentUserProvider _currentUserProvider; 091 092 /** The service extension point. */ 093 protected ServiceExtensionPoint _serviceEP; 094 095 /** The plugin name. */ 096 protected String _pluginName; 097 098 /** The i18n catalogue. */ 099 protected String _i18nCatalogue; 100 101 /** The workflow provider */ 102 protected WorkflowProvider _workflowProvider; 103 104 private org.apache.cocoon.environment.Context _cocoonContext; 105 106 @Override 107 public void setPluginInfo(String pluginName, String featureName, String id) 108 { 109 _pluginName = pluginName; 110 _i18nCatalogue = "plugin." + pluginName; 111 } 112 113 @Override 114 public void contextualize(Context context) throws ContextException 115 { 116 _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 117 } 118 119 @Override 120 public void service(ServiceManager manager) throws ServiceException 121 { 122 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 123 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 124 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 125 _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE); 126 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 127 _serviceEP = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE); 128 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 129 } 130 131 @Override 132 public boolean supports(Event event) 133 { 134 // Listen on site updated because it is too early to listen on sitemap added (we need the site be configured and synchronized in live) 135 return event.getId().equals(ObservationConstants.EVENT_SITE_UPDATED); 136 } 137 138 @Override 139 public int getPriority(Event event) 140 { 141 // Will be processed after live synchronization observers 142 return MAX_PRIORITY + 2000; 143 } 144 145 @Override 146 public void observe(Event event, Map<String, Object> transientVars) throws Exception 147 { 148 Site site = (Site) event.getArguments().get(ObservationConstants.ARGS_SITE); 149 if (site != null) 150 { 151 List<Project> projects = _projectManager.getProjectsForSite(site); 152 if (!projects.isEmpty()) 153 { 154 Project project = projects.get(0); 155 156 // Initialize each sitemap with a set of predefined and configured pages 157 for (Sitemap sitemap : site.getSitemaps()) 158 { 159 _initializeSitemap(sitemap, project); 160 } 161 } 162 } 163 } 164 165 /** 166 * Initialize the given sitemap. 167 * @param sitemap the Sitemap object. 168 * @param project the corresponding project 169 */ 170 protected void _initializeSitemap(Sitemap sitemap, Project project) 171 { 172 // Home page (index) 173 ModifiablePage indexPage = _createPage(sitemap, "index", new I18nizableText(_i18nCatalogue, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_INDEX_TITLE")); 174 if (indexPage != null) 175 { 176 _initializeIndexPage(project, indexPage); 177 } 178 179 _projectManager.initializeModulesSitemap(project, sitemap); 180 181 // Notify of the sitemap change. 182 Map<String, Object> eventParams = new HashMap<>(); 183 eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap); 184 _observationManager.notify(new Event(ObservationConstants.EVENT_SITEMAP_UPDATED, _currentUserProvider.getUser(), eventParams)); 185 } 186 187 /** 188 * Create a new page if not already exists 189 * @param sitemap The sitemap where the page will be created 190 * @param name The page's name 191 * @param i18nTitle The page's title 192 * @return the created page or <code>null</code> if page already exists 193 */ 194 protected ModifiablePage _createPage(Sitemap sitemap, String name, I18nizableText i18nTitle) 195 { 196 if (!sitemap.hasChild(name)) 197 { 198 ModifiablePage page = sitemap.createChild(name, "ametys:defaultPage"); 199 200 // Title should not be missing, but just in case if the i18n message or the whole catalog does not exists in the requested language 201 // to prevent a non-user-friendly error and still generate the project workspace. 202 page.setTitle(StringUtils.defaultIfEmpty(_i18nUtils.translate(i18nTitle, sitemap.getName()), "Missing title")); 203 page.setType(PageType.NODE); 204 page.setSiteName(sitemap.getSiteName()); 205 page.setSitemapName(sitemap.getName()); 206 207 sitemap.saveChanges(); 208 209 return page; 210 } 211 else 212 { 213 return null; 214 } 215 } 216 217 /** 218 * Initialize the index page. 219 * @param project The project 220 * @param indexPage the index page. 221 */ 222 protected void _initializeIndexPage(Project project, ModifiablePage indexPage) 223 { 224 Site site = indexPage.getSite(); 225 Skin skin = _skinsManager.getSkin(site.getSkinId()); 226 227 String filePath = _cocoonContext.getRealPath("/skins/" + skin.getId() + "/conf/project-home-model.xml"); 228 File cfgFile = new File (filePath); 229 230 if (cfgFile.exists()) 231 { 232 _initializeIndexPage(project, skin, indexPage, cfgFile); 233 } 234 else 235 { 236 getLogger().error("The model file '{}' for project home page does not exists. The '%s' page of the project workspace %s will be initialized with default values", filePath, indexPage.getPathInSitemap(), project.getName()); 237 } 238 239 } 240 241 /** 242 * Initialize the index page from a configuration file 243 * @param project the project 244 * @param skin the skin 245 * @param indexPage the index page 246 * @param cfgFile the configuration file 247 */ 248 protected void _initializeIndexPage(Project project, Skin skin, ModifiablePage indexPage, File cfgFile) 249 { 250 try 251 { 252 Configuration configuration = new DefaultConfigurationBuilder().buildFromFile(cfgFile); 253 String templateId = configuration.getAttribute("template", "project-index"); 254 255 SkinTemplate template = skin.getTemplate(templateId); 256 if (template == null) 257 { 258 getLogger().error("The project home page template use an unexsiting template '{}'. The '{}' page of the project workspace '{}' could not be initialized.", templateId, indexPage.getPathInSitemap(), project.getName()); 259 return; 260 } 261 262 // Set the type and template. 263 indexPage.setType(PageType.CONTAINER); 264 indexPage.setTemplate(template.getId()); 265 266 // Initialize the zones. 267 Map<String, SkinTemplateZone> templateZones = template.getZones(); 268 269 for (Configuration zoneConf : configuration.getChildren()) 270 { 271 String zoneName = zoneConf.getAttribute("id"); 272 if (templateZones.containsKey(zoneName)) 273 { 274 ModifiableZone zone = indexPage.createZone(zoneName); 275 276 for (Configuration serviceConf : zoneConf.getChildren("service")) 277 { 278 String serviceId = serviceConf.getAttribute("id"); 279 Configuration paramsConf = serviceConf.getChild("parameters", true); 280 281 Service service = _serviceEP.getExtension(serviceId); 282 283 if (service != null) 284 { 285 ModifiableZoneItem zoneItem = zone.addZoneItem(); 286 zoneItem.setType(ZoneType.SERVICE); 287 zoneItem.setServiceId(serviceId); 288 289 Map<String, ModelItem> serviceModelParams = service.getParameters(); 290 _setServiceParameters(serviceModelParams.values(), zoneItem.getServiceParameters(), paramsConf, indexPage.getSitemapName()); 291 } 292 else 293 { 294 getLogger().error("The project home page template defines an unexsiting service '{}'. The '{}' page of the project workspace {} could not be initialized properly.", serviceId, indexPage.getPathInSitemap(), project.getName()); 295 } 296 } 297 } 298 else 299 { 300 getLogger().error("The project home page template defines an unexsiting zone '{}' for template '{}'. The '{}' page of the project workspace {} could not be initialized properly.", zoneName, templateId, indexPage.getPathInSitemap(), project.getName()); 301 } 302 } 303 304 indexPage.saveChanges(); 305 306 Map<String, Object> eventParams = new HashMap<>(); 307 eventParams.put(ObservationConstants.ARGS_PAGE, indexPage); 308 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams)); 309 } 310 catch (ConfigurationException | SAXException | IOException e) 311 { 312 getLogger().error("Fail to read the project home page model '{}'. The '%s' page of the project workspace %s could not be initialized", cfgFile.getAbsolutePath(), indexPage.getPathInSitemap(), project.getName(), e); 313 } 314 } 315 316 private void _setServiceParameters (Collection<ModelItem> modelItems, ModifiableModelAwareDataHolder dataHolder, Configuration paramsConf, String lang) throws ConfigurationException 317 { 318 for (ModelItem modelItem : modelItems) 319 { 320 String paramName = modelItem.getName(); 321 322 if (modelItem instanceof ElementDefinition) 323 { 324 Configuration paramConf = paramsConf.getChild(paramName, false); 325 if (paramConf != null) 326 { 327 if (((ElementDefinition) modelItem).isMultiple()) 328 { 329 // FIXME we need a XML service descriptor, for now only multiple values of type string are supported 330 if (((ElementDefinition) modelItem).getType() instanceof AbstractStringElementType) 331 { 332 List<String> typedValues = new ArrayList<>(); 333 334 Configuration[] valuesConf = paramConf.getChildren("value"); 335 for (Configuration valueConf : valuesConf) 336 { 337 typedValues.add(valueConf.getValue()); 338 } 339 340 dataHolder.setValue(paramName, typedValues.toArray(new String[typedValues.size()])); 341 } 342 else 343 { 344 getLogger().warn("Only string multiple values are supported in the XML configuration of a service. Can not evaluate the service parameter " + modelItem.getPath()); 345 } 346 347 } 348 else 349 { 350 String paramValue = paramConf.getValue(""); 351 boolean i18n = paramConf.getAttributeAsBoolean("i18n", false); 352 if (i18n) 353 { 354 I18nizableText i18nText = I18nizableText.parseI18nizableText(paramConf, "application"); 355 paramValue = _i18nUtils.translate(i18nText, lang); 356 } 357 358 Object typedValue = ((ElementDefinition) modelItem).getType().castValue(paramValue); 359 dataHolder.setValue(paramName, typedValue); 360 } 361 362 363 } 364 else if (((ElementDefinition) modelItem).getDefaultValue() != null) 365 { 366 // set default value 367 dataHolder.setValue(paramName, ((ServiceParameter) modelItem).getDefaultValue()); 368 } 369 } 370 else if (modelItem instanceof RepeaterDefinition) 371 { 372 Configuration[] entriesConf = paramsConf.getChild(paramName, true).getChildren("entry"); 373 374 List<ModelItem> subModelItems = ((RepeaterDefinition) modelItem).getChildren(); 375 376 int entryCount = 1; 377 for (Configuration entryConf : entriesConf) 378 { 379 ModifiableModelAwareRepeaterEntry entry = dataHolder.getRepeater(paramName, true).addEntry(entryCount); 380 _setServiceParameters(subModelItems, entry, entryConf, lang); 381 entryCount++; 382 } 383 } 384 } 385 386 } 387}