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