001/* 002 * Copyright 2013 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.cms.search.ui.model.impl; 017 018import java.util.Arrays; 019import java.util.Collection; 020import java.util.HashSet; 021import java.util.LinkedHashSet; 022import java.util.List; 023import java.util.Locale; 024import java.util.Map; 025import java.util.Set; 026import java.util.stream.Collectors; 027 028import org.apache.avalon.framework.activity.Disposable; 029import org.apache.avalon.framework.configuration.Configurable; 030import org.apache.avalon.framework.configuration.Configuration; 031import org.apache.avalon.framework.configuration.ConfigurationException; 032import org.apache.avalon.framework.context.Context; 033import org.apache.avalon.framework.context.ContextException; 034import org.apache.avalon.framework.context.Contextualizable; 035import org.apache.avalon.framework.service.ServiceException; 036import org.apache.avalon.framework.service.ServiceManager; 037import org.apache.avalon.framework.service.Serviceable; 038import org.apache.commons.lang3.StringUtils; 039import org.slf4j.Logger; 040 041import org.ametys.cms.content.ContentHelper; 042import org.ametys.cms.contenttype.ContentType; 043import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 044import org.ametys.cms.contenttype.ContentTypesHelper; 045import org.ametys.cms.repository.Content; 046import org.ametys.cms.search.SearchField; 047import org.ametys.cms.search.model.SystemProperty; 048import org.ametys.cms.search.model.SystemProperty.EnumeratorDefinition; 049import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 050import org.ametys.cms.search.model.SystemResultField; 051import org.ametys.cms.search.ui.model.SearchUIColumn; 052import org.ametys.plugins.repository.AmetysObjectResolver; 053import org.ametys.plugins.repository.model.RepeaterDefinition; 054import org.ametys.runtime.i18n.I18nizableText; 055import org.ametys.runtime.model.ElementDefinition; 056import org.ametys.runtime.model.ModelHelper; 057import org.ametys.runtime.model.ModelItem; 058import org.ametys.runtime.parameter.Enumerator; 059import org.ametys.runtime.parameter.StaticEnumerator; 060import org.ametys.runtime.plugin.component.LogEnabled; 061import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 062 063/** 064 * This class is a search column on a system property (author, lastModified, with-comments, ...) 065 */ 066public class SystemSearchUIColumn extends AbstractSearchUIColumn implements SystemResultField, Contextualizable, LogEnabled, Configurable, Serviceable, Disposable 067{ 068 069 /** ComponentManager for {@link Enumerator}s. */ 070 protected ThreadSafeComponentManager<Enumerator> _enumeratorManager; 071 072 /** The system property extension point. */ 073 protected SystemPropertyExtensionPoint _systemPropEP; 074 075 /** The content type extension point. */ 076 protected ContentTypeExtensionPoint _cTypeEP; 077 078 /** The content helper. */ 079 protected ContentHelper _contentHelper; 080 081 /** The content type helper. */ 082 protected ContentTypesHelper _contentTypeHelper; 083 084 /** The Ametys object resolver. */ 085 protected AmetysObjectResolver _resolver; 086 087 /** The system property. */ 088 protected SystemProperty _systemProperty; 089 090 /** The join paths. */ 091 protected String _joinPaths; 092 093 /** The content types */ 094 private Set<String> _contentTypes; 095 096 private ServiceManager _manager; 097 private Logger _logger; 098 private Context _context; 099 100 @Override 101 public void contextualize(Context context) throws ContextException 102 { 103 _context = context; 104 } 105 106 @Override 107 public void setLogger(Logger logger) 108 { 109 _logger = logger; 110 } 111 112 @Override 113 public void service(ServiceManager manager) throws ServiceException 114 { 115 _manager = manager; 116 _systemPropEP = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE); 117 _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 118 _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE); 119 _contentTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 120 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 121 } 122 123 @Override 124 public void dispose() 125 { 126 _enumeratorManager.dispose(); 127 _enumeratorManager = null; 128 } 129 130 @Override 131 public void configure(Configuration configuration) throws ConfigurationException 132 { 133 try 134 { 135 _enumeratorManager = new ThreadSafeComponentManager<>(); 136 _enumeratorManager.setLogger(_logger); 137 _enumeratorManager.contextualize(_context); 138 _enumeratorManager.service(_manager); 139 140 String fullPath = configuration.getChild("systemProperty").getAttribute("name"); 141 int pos = fullPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR); 142 143 String systemPropertyId = pos > -1 ? fullPath.substring(pos + ModelItem.ITEM_PATH_SEPARATOR.length()) : fullPath; 144 145 if (!_systemPropEP.isDisplayable(systemPropertyId)) 146 { 147 throw new ConfigurationException("The property '" + systemPropertyId + "' doesn't exist or is not displayable."); 148 } 149 150 _joinPaths = pos > -1 ? fullPath.substring(0, pos) : ""; 151 _systemProperty = _systemPropEP.getExtension(systemPropertyId); 152 153 _contentTypes = new HashSet<>(); 154 for (Configuration cTypeConf : configuration.getChild("contentTypes").getChildren("type")) 155 { 156 _contentTypes.add(cTypeConf.getAttribute("id")); 157 } 158 159 setId(fullPath); 160 161 setLabel(_systemProperty.getLabel()); 162 setDescription(_systemProperty.getDescription()); 163 setType(_systemProperty.getType()); 164 165 Set<String> baseContentTypeIds = new HashSet<>(); 166 for (Configuration cTypeConf : configuration.getChild("contentTypes").getChildren("baseType")) 167 { 168 baseContentTypeIds.add(cTypeConf.getAttribute("id")); 169 } 170 boolean multiple = testMultiple(baseContentTypeIds); 171 setMultiple(multiple); 172 173 String enumeratorRole = null; 174 EnumeratorDefinition enumDef = _systemProperty.getEnumeratorDefinition(configuration); 175 if (enumDef != null) 176 { 177 enumeratorRole = _initializeEnumerator(enumDef); 178 if (enumeratorRole != null) 179 { 180 _enumeratorManager.initialize(); 181 setEnumerator(_enumeratorManager.lookup(enumeratorRole)); 182 } 183 } 184 185 setEditable(false); 186 setWidth(_systemProperty.getColumnWidth() != null ? _systemProperty.getColumnWidth() : 150); 187 setHidden(configuration.getChild("hidden").getValueAsBoolean(false)); 188 setSortable(configuration.getChild("sortable").getValueAsBoolean(_systemProperty.isSortable())); 189 if (isSortable()) 190 { 191 setDefaultSorter(configuration.getChild("default-sorter").getValue(null)); 192 } 193 // Potentially replace the standard label and description by the custom ones. 194 I18nizableText userLabel = _configureI18nizableText(configuration.getChild("label", false), null); 195 if (userLabel != null) 196 { 197 setLabel(userLabel); 198 } 199 I18nizableText userDescription = _configureI18nizableText(configuration.getChild("description", false), null); 200 if (userDescription != null) 201 { 202 setDescription(userDescription); 203 } 204 205 if (_systemProperty.getRenderer() != null) 206 { 207 setRenderer(_systemProperty.getRenderer()); 208 } 209 if (_systemProperty.getConverter() != null) 210 { 211 setConverter(_systemProperty.getConverter()); 212 } 213 } 214 catch (Exception e) 215 { 216 throw new ConfigurationException("Error configuring the system search column.", configuration, e); 217 } 218 } 219 220 private boolean testMultiple(Set<String> contentTypeIds) 221 { 222 // if system property is multiple, then the column is multiple 223 if (_systemProperty.isMultiple()) 224 { 225 return true; 226 } 227 228 // Otherwise, the column is multiple if at least one of the join path definitions is multiple 229 if (!contentTypeIds.isEmpty() && StringUtils.isNotEmpty(_joinPaths)) 230 { 231 Collection<ContentType> contentTypes = contentTypeIds.stream() 232 .map(_cTypeEP::getExtension) 233 .collect(Collectors.toList()); 234 List<ModelItem> modelItems = ModelHelper.getAllModelItemsInPath(_joinPaths, contentTypes); 235 236 for (ModelItem definition : modelItems) 237 { 238 if (definition instanceof ElementDefinition && ((ElementDefinition) definition).isMultiple() 239 || definition instanceof RepeaterDefinition) 240 { 241 return true; 242 } 243 } 244 } 245 246 return false; 247 } 248 249 @Override 250 public String getFieldPath() 251 { 252 return (_joinPaths.isEmpty() ? "" : _joinPaths + ModelItem.ITEM_PATH_SEPARATOR) + getSystemPropertyId(); 253 } 254 255 /** 256 * Get the system property name 257 * @return The property name 258 */ 259 public String getSystemPropertyId() 260 { 261 return _systemProperty.getId(); 262 } 263 264 @Override 265 public Object getValue(Content content, Locale defaultLocale) 266 { 267 return _getValue(content, false); 268 } 269 270 @Override 271 public Object getFullValue(Content content, Locale defaultLocale) 272 { 273 return _getValue(content, true); 274 } 275 276 private Object _getValue(Content content, boolean full) 277 { 278 if (isMultiple()) 279 { 280 Set<Object> values = new LinkedHashSet<>(); 281 282 for (Content targetContent : _contentHelper.getTargetContents(content, _joinPaths)) 283 { 284 Object value = full ? _systemProperty.getJsonValue(targetContent, true) : _systemProperty.getValue(targetContent); 285 if (value != null) 286 { 287 if (value instanceof Collection<?>) 288 { 289 // Flatten values 290 values.addAll((Collection<?>) value); 291 } 292 else if (value instanceof Object[]) 293 { 294 // Flatten values 295 values.addAll(Arrays.asList((Object[]) value)); 296 } 297 else 298 { 299 values.add(value); 300 } 301 } 302 } 303 304 return values; 305 } 306 else 307 { 308 Content targetContent = _contentHelper.getTargetContent(content, _joinPaths); 309 if (targetContent != null) 310 { 311 return full ? _systemProperty.getJsonValue(targetContent, true) : _systemProperty.getValue(targetContent); 312 } 313 } 314 315 return null; 316 } 317 318 @Override 319 public SearchField getSearchField() 320 { 321 return _systemProperty.getSearchField(); 322 } 323 324 private String _initializeEnumerator(EnumeratorDefinition enumDef) 325 { 326 String role = null; 327 328 if (enumDef.isStatic()) 329 { 330 StaticEnumerator enumerator = new StaticEnumerator(); 331 enumDef.getStaticEntries().entrySet().forEach(entry -> enumerator.add(entry.getValue(), entry.getKey())); 332 setEnumerator(enumerator); 333 } 334 else 335 { 336 role = "enumerator"; 337 _enumeratorManager.addComponent("cms", null, role, enumDef.getEnumeratorClass(), enumDef.getConfiguration()); 338 } 339 340 return role; 341 } 342 343 public Map<String, SearchUIColumn> getSubColumns() 344 { 345 return null; 346 } 347 348}