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