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