001/* 002 * Copyright 2010 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.web.filter; 017 018import java.time.LocalDate; 019import java.time.ZoneId; 020import java.time.format.DateTimeParseException; 021import java.util.ArrayList; 022import java.util.Date; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import org.apache.avalon.framework.configuration.Configurable; 028import org.apache.avalon.framework.configuration.Configuration; 029import org.apache.avalon.framework.configuration.ConfigurationException; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033 034import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 035import org.ametys.cms.filter.StaticContentFilter.DynamicDateExpression; 036import org.ametys.cms.tag.TagProviderExtensionPoint; 037import org.ametys.plugins.repository.AmetysObjectResolver; 038import org.ametys.plugins.repository.query.SortCriteria; 039import org.ametys.plugins.repository.query.expression.AndExpression; 040import org.ametys.plugins.repository.query.expression.DateExpression; 041import org.ametys.plugins.repository.query.expression.Expression; 042import org.ametys.plugins.repository.query.expression.Expression.Operator; 043import org.ametys.plugins.repository.query.expression.OrExpression; 044import org.ametys.runtime.i18n.I18nizableText; 045import org.ametys.runtime.plugin.component.PluginAware; 046import org.ametys.web.repository.site.SiteManager; 047 048/** 049 * This class represents a static filter for contents 050 * 051 */ 052public class StaticWebContentFilter extends DefaultWebContentFilter implements Configurable, PluginAware, Serviceable 053{ 054 /** The plugin name */ 055 protected String _pluginName; 056 /** The feature name */ 057 protected String _featureName; 058 059 /** 060 * Constructor 061 */ 062 public StaticWebContentFilter () 063 { 064 // Empty construction needed for component 065 super(); 066 } 067 068 /** 069 * Constructor 070 * @param id The filter id 071 * @param resolver The ametys object resolver 072 * @param contentTypeExtensionPoint The extension point for content types 073 * @param siteManager The site manager 074 * @param tagProviderEP The tag provider 075 */ 076 public StaticWebContentFilter(String id, AmetysObjectResolver resolver, ContentTypeExtensionPoint contentTypeExtensionPoint, SiteManager siteManager, TagProviderExtensionPoint tagProviderEP) 077 { 078 super(id, resolver, contentTypeExtensionPoint, siteManager, tagProviderEP); 079 } 080 081 /** 082 * Creates a new filter from copy of another 083 * @param id The filter unique identifier 084 * @param originalFilter The original filter to be copied 085 * @param resolver The ametys object resolver 086 * @param contentTypeExtensionPoint The extension point for content types 087 * @param siteManager The site manager 088 * @param tagProviderEP The tag provider 089 */ 090 public StaticWebContentFilter(String id, StaticWebContentFilter originalFilter, AmetysObjectResolver resolver, ContentTypeExtensionPoint contentTypeExtensionPoint, SiteManager siteManager, TagProviderExtensionPoint tagProviderEP) 091 { 092 super(id, originalFilter, resolver, contentTypeExtensionPoint, siteManager, tagProviderEP); 093 } 094 095 @Override 096 public void service(ServiceManager smanager) throws ServiceException 097 { 098 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 099 _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 100 } 101 102 @Override 103 public void setPluginInfo(String pluginName, String featureName, String id) 104 { 105 _pluginName = pluginName; 106 _featureName = featureName; 107 _id = id; 108 } 109 110 @Override 111 public void configure(Configuration configuration) throws ConfigurationException 112 { 113 _title = _configureTitle (configuration.getChild("title", true)); 114 _description = _configureDescription (configuration.getChild("description", true)); 115 _contentTypes = _configureContentTypes(configuration.getChild("content-types", true)); 116 _metadataSetName = configuration.getChild("view").getValue("main"); 117 _length = configuration.getChild("max-result", true).getValueAsInteger(Integer.MAX_VALUE); 118 _metadata = _configureMetadata(configuration.getChild("metadata", true)); 119 _metadataCondition = Condition.valueOf(configuration.getChild("metadata", true).getAttribute("condition", "AND").toUpperCase()); 120 _additionalFilterExpression = _configureComplexMetadata(configuration.getChild("metadata"), _metadataCondition); 121 _sortCriteria = _configureSortCriteria(configuration.getChild("sort-information")); 122 _maskOrphan = configuration.getChild("mask-orphan", true).getValueAsBoolean(false); 123 if (!_maskOrphan) 124 { 125 // @Deprecated 126 _maskOrphan = configuration.getAttributeAsBoolean("maskOrphan", false); 127 } 128 boolean handleUserAccess = configuration.getChild("handle-user-access").getValueAsBoolean(false); 129 _accessLimitation = handleUserAccess ? AccessLimitation.USER_ACCESS : AccessLimitation.PAGE_ACCESS; 130 131 _configureSearchContexts(configuration); 132 } 133 134 /** 135 * Configure the filter's title 136 * @param configuration The title configuration 137 * @return The filter's title 138 * @throws ConfigurationException If an error occurs 139 */ 140 protected I18nizableText _configureTitle (Configuration configuration) throws ConfigurationException 141 { 142 if (configuration.getAttributeAsBoolean("i18n", false)) 143 { 144 return new I18nizableText("plugin." + _pluginName, configuration.getValue()); 145 } 146 else 147 { 148 return new I18nizableText(configuration.getValue("")); 149 } 150 } 151 152 /** 153 * Configure the filter's description 154 * @param configuration The description configuration 155 * @return The filter's description 156 * @throws ConfigurationException If an error occurs 157 */ 158 protected I18nizableText _configureDescription (Configuration configuration) throws ConfigurationException 159 { 160 if (configuration.getAttributeAsBoolean("i18n", false)) 161 { 162 return new I18nizableText("plugin." + _pluginName, configuration.getValue()); 163 } 164 else 165 { 166 return new I18nizableText(configuration.getValue("")); 167 } 168 } 169 170 /** 171 * Configure the content type ids 172 * @param configuration The content types configuration 173 * @return The set of content type ids 174 * @throws ConfigurationException If an error occurs 175 */ 176 protected List<String> _configureContentTypes(Configuration configuration) throws ConfigurationException 177 { 178 List<String> cTypes = new ArrayList<>(); 179 for (Configuration cType : configuration.getChildren("type")) 180 { 181 cTypes.add(cType.getAttribute("id")); 182 } 183 return cTypes; 184 } 185 186 /** 187 * Configure the search contexts. 188 * @param configuration the filter configuration. 189 * @throws ConfigurationException if an error occurs. 190 */ 191 protected void _configureSearchContexts(Configuration configuration) throws ConfigurationException 192 { 193 FilterSearchContext params = addSearchContext(); 194 195 Configuration tagsConf = configuration.getChild("tags", true); 196 for (Configuration tag : tagsConf.getChildren("tag")) 197 { 198 params.addTag(tag.getAttribute("key")); 199 } 200 Condition condition = Condition.valueOf(tagsConf.getAttribute("condition", "AND").toUpperCase()); 201 boolean strict = tagsConf.getAttributeAsBoolean("strict", true); 202 Context context = _configureContext(configuration.getChild("context", true)); 203 ContextLanguage contextLang = _configureContextLanguage(configuration.getChild("context", true)); 204 int depth = _configureDepth(configuration.getChild("context", true)); 205 206 params.setContext(context); 207 params.setContextLanguage(contextLang); 208 params.setDepth(depth); 209 params.setTagsCondition(condition); 210 params.setTagsAutoPosting(!strict); 211 } 212 213 /** 214 * Configure simple metadata clauses (fixed string values). 215 * @param configuration The metadata configuration 216 * @return The metadata to filter by, as a Map of metadata name -> value. 217 * @throws ConfigurationException If an error occurs 218 */ 219 protected Map<String, String> _configureMetadata(Configuration configuration) throws ConfigurationException 220 { 221 Map<String, String> metadata = new HashMap<>(); 222 for (Configuration elt : configuration.getChildren("metadata")) 223 { 224 String op = elt.getAttribute("operator", null); 225 String type = elt.getAttribute("type", null); 226 227 // Process only conditions without type and operator. 228 if (op == null && type == null) 229 { 230 metadata.put(elt.getAttribute("id"), elt.getValue(null)); 231 } 232 } 233 return metadata; 234 } 235 236 /** 237 * Configure complex metadata conditions (may filter on non-string metadata, 238 * and not limited to equality.) 239 * @param configuration The metadata conditions configuration. 240 * @param metadataOperator the metadata operator. 241 * @return An expression for complex metadata conditions. 242 * @throws ConfigurationException If an error occurs 243 */ 244 protected Expression _configureComplexMetadata(Configuration configuration, Condition metadataOperator) throws ConfigurationException 245 { 246 List<Expression> expressions = new ArrayList<>(); 247 248 for (Configuration elt : configuration.getChildren("metadata")) 249 { 250 String op = elt.getAttribute("operator", null); 251 String type = elt.getAttribute("type", null); 252 253 // Process only metadata with a type and operator. 254 if (op != null && type != null) 255 { 256 String id = elt.getAttribute("id"); 257 String value = elt.getValue(""); 258 259 // Date expression. 260 if (type.equals("date")) 261 { 262 expressions.add(_getComplexDateExpression(id, op, value, elt)); 263 } 264 } 265 else if ((op == null && type != null) || (op != null && type == null)) 266 { 267 throw new ConfigurationException("Both type and operator must be specified for complex metadata conditions.", elt); 268 } 269 } 270 271 Expression[] exprArr = expressions.toArray(new Expression[expressions.size()]); 272 273 Expression expr = null; 274 if (metadataOperator == Condition.AND) 275 { 276 expr = new AndExpression(exprArr); 277 } 278 else if (metadataOperator == Condition.OR) 279 { 280 expr = new OrExpression(exprArr); 281 } 282 283 return expr; 284 } 285 286 /** 287 * Get a complex date metadata expression. 288 * @param metadataName the metadata name. 289 * @param operator the comparison operator. 290 * @param value the compared value. 291 * @param configuration the configuration being processed. 292 * @return the date Expression. 293 * @throws ConfigurationException if the configuration is not valid. 294 */ 295 protected Expression _getComplexDateExpression(String metadataName, String operator, String value, Configuration configuration) throws ConfigurationException 296 { 297 int operatorOffset = 0; 298 299 Operator op = Operator.EQ; 300 if ("eq".equalsIgnoreCase(operator)) 301 { 302 op = Operator.EQ; 303 } 304 else if ("gte".equalsIgnoreCase(operator)) 305 { 306 op = Operator.GE; 307 } 308 else if ("gt".equalsIgnoreCase(operator)) 309 { 310 op = Operator.GE; 311 operatorOffset = 1; 312 } 313 else if ("lte".equalsIgnoreCase(operator)) 314 { 315 op = Operator.LT; 316 operatorOffset = 1; 317 } 318 else if ("lt".equalsIgnoreCase(operator)) 319 { 320 op = Operator.LT; 321 } 322 else 323 { 324 throw new ConfigurationException("Invalid date operator: '" + operator + "'", configuration); 325 } 326 327 // Date value can be one of "now"/"today", "2013-09-07", 328 // or "-7" to specify a date relative to the current date. 329 Date date = null; 330 Integer valueOffset = null; 331 if ("now".equalsIgnoreCase(value) || "today".equalsIgnoreCase(value)) 332 { 333 // "now" or "today". 334 valueOffset = 0; 335 } 336 else 337 { 338 try 339 { 340 // Try to parse as a fixed date. 341 date = Date.from(LocalDate.parse(value).atStartOfDay(ZoneId.systemDefault()).plusDays(operatorOffset).toInstant()); 342 } 343 catch (DateTimeParseException | IllegalArgumentException e) 344 { 345 try 346 { 347 // If not a fixed date, try to parse as an offset (signed integer). 348 valueOffset = Integer.parseInt(value); 349 } 350 catch (NumberFormatException nfe) 351 { 352 // Ignore: leave date value null to throw an exception. 353 } 354 } 355 } 356 357 if (date != null) 358 { 359 // Fixed date: standard date expression. 360 return new DateExpression(metadataName, op, date); 361 } 362 else if (valueOffset != null) 363 { 364 // Offset: dynamic date expression. 365 int offset = operatorOffset + valueOffset; 366 return new DynamicDateExpression(metadataName, op, offset); 367 } 368 else 369 { 370 throw new ConfigurationException("Invalid date value: '" + value + "'", configuration); 371 } 372 } 373 374 /** 375 * Configure the sort criteria 376 * @param configuration The sort criteria configuration 377 * @return The sort criteria 378 * @throws ConfigurationException If an error occurs 379 */ 380 protected SortCriteria _configureSortCriteria(Configuration configuration) throws ConfigurationException 381 { 382 SortCriteria sortCriteria = null; 383 if (configuration != null) 384 { 385 sortCriteria = new SortCriteria(); 386 for (Configuration sort : configuration.getChildren("sort")) 387 { 388 sortCriteria.addCriterion(sort.getAttribute("metadataId"), sort.getAttributeAsBoolean("ascending", false), sort.getAttributeAsBoolean("lower-case", false)); 389 } 390 } 391 return sortCriteria; 392 } 393 394 /** 395 * Configure the context search 396 * @param configuration The context configuration 397 * @return The search context 398 * @throws ConfigurationException If an error occurs 399 */ 400 protected Context _configureContext (Configuration configuration) throws ConfigurationException 401 { 402 String context = configuration.getAttribute("type", "current-site"); 403 if (context.equals(Context.OTHER_SITES.toString())) 404 { 405 return Context.OTHER_SITES; 406 } 407 else if (context.equals(Context.SITES.toString())) 408 { 409 return Context.SITES; 410 } 411 else if (context.equals(Context.CHILD_PAGES.toString())) 412 { 413 return Context.CHILD_PAGES; 414 } 415 else if (context.equals(Context.SITES_LIST.toString())) 416 { 417 return Context.SITES_LIST; 418 } 419 420 return Context.CURRENT_SITE; 421 } 422 423 /** 424 * Configure the context language 425 * @param configuration The context configuration 426 * @return The context language 427 * @throws ConfigurationException If an error occurs 428 */ 429 protected ContextLanguage _configureContextLanguage (Configuration configuration) throws ConfigurationException 430 { 431 String context = configuration.getAttribute("lang", "current"); 432 if (context.equals(ContextLanguage.OTHERS.toString())) 433 { 434 return ContextLanguage.OTHERS; 435 } 436 else if (context.equals(ContextLanguage.ALL.toString())) 437 { 438 return ContextLanguage.ALL; 439 } 440 441 return ContextLanguage.CURRENT; 442 } 443 444 /** 445 * Configure the depth search 446 * @param configuration The depth configuration 447 * @return The depth 448 * @throws ConfigurationException If an error occurs 449 */ 450 protected int _configureDepth (Configuration configuration) throws ConfigurationException 451 { 452 return configuration.getAttributeAsInteger("depth", 0); 453 } 454}