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