001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.file.collectors; 029 030import org.opencms.file.CmsDataAccessException; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.file.CmsResourceFilter; 034import org.opencms.file.I_CmsResource; 035import org.opencms.file.types.I_CmsResourceType; 036import org.opencms.loader.CmsLoaderException; 037import org.opencms.main.CmsException; 038import org.opencms.main.CmsLog; 039import org.opencms.main.OpenCms; 040import org.opencms.relations.CmsCategory; 041import org.opencms.relations.CmsCategoryService; 042import org.opencms.util.CmsStringUtil; 043 044import java.util.ArrayList; 045import java.util.Arrays; 046import java.util.Collections; 047import java.util.Comparator; 048import java.util.HashMap; 049import java.util.Iterator; 050import java.util.List; 051import java.util.Map; 052 053import org.apache.commons.logging.Log; 054 055/** 056 * A collector to fetch XML contents in a folder or the current site filtered by one or more given category types.<p> 057 * 058 * The return list will also be filtered by given key value pairs which are given as a collector parameter.<p> 059 * 060 * Usage: 061 * <code> 062 * <cms:contentload collector="allKeyValuePairFiltered" param="resource=[filename]|resourceType=[resource type]|categoryTypes=[category1,category2,...]|subTree=[boolean]|sortBy=[category|date|property:[property_name]]|sortAsc=[boolean]"> 063 * </code> 064 * 065 * @since 7.0.0 066 */ 067public class CmsCategoryResourceCollector extends A_CmsResourceCollector { 068 069 /** 070 * Data structure for the collector, parsed from the collector parameters.<p> 071 * 072 * In addition to the superclass this implementation accepts parameters that build key value pairs separated by 073 * pipes '|', which allows arbitrary order of parameters and free numbers of parameters.<p> 074 * 075 * Usage: 076 * <code> 077 * "resource=[filename]|resourceType=[resource type]|categoryTypes=[category1,category2,...]|excludeTimerange=false|subTree=[boolean]|sortBy=[category|date|property:[property_name]]|sortAsc=[boolean]" 078 * </code> 079 */ 080 private static final class CmsCategoryCollectorData extends CmsCollectorData { 081 082 /** The collector parameter key for the resource type. */ 083 public static final String PARAM_KEY_CATEGORY_TYPES = "categoryTypes"; 084 085 /** The collector parameter key for the count. */ 086 public static final String PARAM_KEY_COUNT = "count"; 087 088 /** The collector parameter key for the resource (folder / file). */ 089 public static final String PARAM_KEY_RESOURCE = "resource"; 090 091 /** The collector parameter key for the resource type. */ 092 public static final String PARAM_KEY_RESOURCE_TYPE = "resourceType"; 093 094 /** The collector parameter key for sort ascending. */ 095 public static final String PARAM_KEY_SORT_ASC = "sortAsc"; 096 097 /** The collector parameter key for sort by. */ 098 public static final String PARAM_KEY_SORT_BY = "sortBy"; 099 100 /** The collector parameter key for the sub tree. */ 101 public static final String PARAM_KEY_SUB_TREE = "subTree"; 102 103 /** The list of category types. */ 104 private List<String> m_categoryTypes; 105 106 /** Indicates if the returned list will be sorted ascending or not (descending). */ 107 private boolean m_sortAsc; 108 109 /** The returned list will be sort by this ('category', 'date' or 'property' are excepted). */ 110 private String m_sortBy; 111 112 /** The returned list will be sort by this property value. */ 113 private String m_sortByPropertyName; 114 115 /** Indicates if the sub tree of the given resource will be searched for appropriate resources too. */ 116 private boolean m_subTree; 117 118 /** 119 * Creates a new collector data set.<p> 120 * 121 * @param data the data to parse. 122 * 123 * @throws CmsLoaderException if the given configuration is not valid. 124 */ 125 public CmsCategoryCollectorData(String data) 126 throws CmsLoaderException { 127 128 parseExtendedData(data); 129 } 130 131 /** 132 * Returns the list of requested categories.<p> 133 * 134 * @return the list of requested categories 135 */ 136 public List<String> getCategoryTypes() { 137 138 return m_categoryTypes; 139 } 140 141 /** 142 * Returns the sort by string (only 'date', 'category' or 'property' excepted).<p> 143 * 144 * @return the sort by string 145 */ 146 public String getSortBy() { 147 148 return m_sortBy; 149 } 150 151 /** 152 * Returns the sort by this property value.<p> 153 * 154 * @return the sort by string 155 */ 156 public String getSortByPropertyName() { 157 158 return m_sortByPropertyName; 159 } 160 161 /** 162 * Returns the sort order. <code>true=asc</code> or <code>false=desc</code> <p> 163 * 164 * @return the sort order. <code>true=asc</code> or <code>false=desc</code> 165 */ 166 public boolean getSortOrder() { 167 168 return m_sortAsc; 169 } 170 171 /** 172 * Returns <code>true</code> if the list has to be sorted in ascending order.<p> 173 * 174 * @return <code>true</code> if the list has to be sorted in ascending order 175 */ 176 public boolean isSortAsc() { 177 178 return m_sortAsc; 179 } 180 181 /** 182 * Returns <code>true</code> if the sub tree of the given resource will be searched too.<p> 183 * 184 * @return <code>true</code> if the sub tree of the given resource will be searched too. 185 */ 186 public boolean isSubTree() { 187 188 return m_subTree; 189 } 190 191 /** 192 * Parses the additional configuration data from the collector param.<p> 193 * 194 * @param data the configuration data. 195 * 196 * @throws CmsLoaderException if something goes wrong 197 */ 198 private void parseExtendedData(String data) throws CmsLoaderException { 199 200 String[] keyValueTokens = CmsStringUtil.splitAsArray(data, '|'); 201 setType(-1); 202 for (int i = keyValueTokens.length - 1; i >= 0; i--) { 203 String relation = keyValueTokens[i]; 204 String[] keyValuePair = CmsStringUtil.splitAsArray(relation, '='); 205 206 String key = keyValuePair[0]; 207 String value = keyValuePair[1]; 208 209 if (PARAM_KEY_CATEGORY_TYPES.equals(key)) { 210 m_categoryTypes = CmsStringUtil.splitAsList(value, ','); 211 } else if (PARAM_KEY_RESOURCE.equals(key)) { 212 setFileName(value); 213 } else if (PARAM_KEY_RESOURCE_TYPE.equals(key)) { 214 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(value); 215 if (type != null) { 216 setType(type.getTypeId()); 217 } 218 } else if (PARAM_KEY_SORT_ASC.equals(key)) { 219 m_sortAsc = Boolean.valueOf(value).booleanValue(); 220 } else if (PARAM_KEY_SORT_BY.equals(key)) { 221 if (value.contains(":")) { 222 String[] keyValuePairProp = CmsStringUtil.splitAsArray(value, ':'); 223 String keyProp = keyValuePairProp[0]; 224 String valueProp = keyValuePairProp[1]; 225 m_sortBy = keyProp; 226 m_sortByPropertyName = valueProp; 227 } else { 228 m_sortBy = value; 229 m_sortByPropertyName = null; 230 } 231 } else if (PARAM_KEY_SUB_TREE.equals(key)) { 232 m_subTree = Boolean.valueOf(value).booleanValue(); 233 } else if (PARAM_KEY_COUNT.equals(key)) { 234 int count = 0; 235 try { 236 count = Integer.parseInt(value); 237 } catch (NumberFormatException e) { 238 // ignore 239 } 240 setCount(count); 241 } else if (PARAM_EXCLUDETIMERANGE.equalsIgnoreCase(key)) { 242 setExcludeTimerange(Boolean.valueOf(value).booleanValue()); 243 } else { 244 LOG.error("Unknow key found in collector parameters."); 245 } 246 } 247 } 248 } 249 250 /** Compares the release date of resources in descending order. */ 251 public static final Comparator<CmsResource> COMPARE_DATE_RELEASED_DESC = new Comparator<CmsResource>() { 252 253 /** 254 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 255 */ 256 public int compare(CmsResource r1, CmsResource r2) { 257 258 if (r1 == r2) { 259 return 0; 260 } 261 262 long date1 = r1.getDateReleased(); 263 if (date1 == CmsResource.DATE_RELEASED_DEFAULT) { 264 // use creation date if release date is not set 265 date1 = r1.getDateLastModified(); 266 } 267 268 long date2 = r2.getDateReleased(); 269 if (date2 == CmsResource.DATE_RELEASED_DEFAULT) { 270 // use creation date if release date is not set 271 date2 = r2.getDateLastModified(); 272 } 273 274 return (date1 > date2) ? 1 : (date1 < date2) ? -1 : 0; 275 } 276 }; 277 278 /** The log object for this class. */ 279 protected static final Log LOG = CmsLog.getLog(CmsCategoryResourceCollector.class); 280 281 /** Static array of the collectors implemented by this class. */ 282 private static final String[] COLLECTORS = {"allKeyValuePairFiltered"}; 283 284 /** Array list for fast collector name lookup. */ 285 private static final List<String> COLLECTORS_LIST = Collections.unmodifiableList(Arrays.asList(COLLECTORS)); 286 287 /** 288 * @see org.opencms.file.collectors.I_CmsResourceCollector#getCollectorNames() 289 */ 290 public List<String> getCollectorNames() { 291 292 return COLLECTORS_LIST; 293 } 294 295 /** 296 * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateLink(org.opencms.file.CmsObject, java.lang.String, java.lang.String) 297 */ 298 public String getCreateLink(CmsObject cms, String collectorName, String param) 299 throws CmsException, CmsDataAccessException { 300 301 // if action is not set, use default action 302 if (collectorName == null) { 303 collectorName = COLLECTORS[0]; 304 } 305 306 switch (COLLECTORS_LIST.indexOf(collectorName)) { 307 case 0: 308 // "allKeyValuePairFiltered" 309 return null; 310 default: 311 throw new CmsDataAccessException( 312 Messages.get().container(Messages.ERR_COLLECTOR_NAME_INVALID_1, collectorName)); 313 } 314 } 315 316 /** 317 * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateParam(org.opencms.file.CmsObject, java.lang.String, java.lang.String) 318 */ 319 public String getCreateParam(CmsObject cms, String collectorName, String param) throws CmsDataAccessException { 320 321 // if action is not set, use default action 322 if (collectorName == null) { 323 collectorName = COLLECTORS[0]; 324 } 325 326 switch (COLLECTORS_LIST.indexOf(collectorName)) { 327 case 0: 328 // "allKeyValuePairFiltered" 329 return null; 330 default: 331 throw new CmsDataAccessException( 332 Messages.get().container(Messages.ERR_COLLECTOR_NAME_INVALID_1, collectorName)); 333 } 334 } 335 336 /** 337 * @see org.opencms.file.collectors.I_CmsResourceCollector#getResults(org.opencms.file.CmsObject, java.lang.String, java.lang.String) 338 */ 339 public List<CmsResource> getResults(CmsObject cms, String collectorName, String param) 340 throws CmsDataAccessException, CmsException { 341 342 return getResults(cms, collectorName, param, -1); 343 } 344 345 /** 346 * @see org.opencms.file.collectors.I_CmsResourceCollector#getResults(org.opencms.file.CmsObject, java.lang.String, java.lang.String) 347 */ 348 public List<CmsResource> getResults(CmsObject cms, String collectorName, String param, int numResults) 349 throws CmsDataAccessException, CmsException { 350 351 // if action is not set use default 352 if (collectorName == null) { 353 collectorName = COLLECTORS[0]; 354 } 355 356 switch (COLLECTORS_LIST.indexOf(collectorName)) { 357 358 case 0: 359 // "allKeyValuePairFiltered" 360 return allKeyValuePairFiltered(cms, param, numResults); 361 default: 362 throw new CmsDataAccessException( 363 Messages.get().container(Messages.ERR_COLLECTOR_NAME_INVALID_1, collectorName)); 364 } 365 } 366 367 /** 368 * Collects all resources for the given categories filtered and sorted by the given collector parameter.<p> 369 * 370 * @param cms the current OpenCms user context 371 * @param param value parameter to filter the resources 372 * @param numResults number of results 373 * 374 * @return a list of resources filtered and sorted by the given collector parameter 375 * 376 * @throws CmsException if something goes wrong 377 */ 378 protected List<CmsResource> allKeyValuePairFiltered(CmsObject cms, String param, int numResults) 379 throws CmsException { 380 381 CmsCategoryCollectorData data = new CmsCategoryCollectorData(param); 382 383 if ((data.getCategoryTypes() != null) && (data.getCategoryTypes().size() > 0)) { 384 List<CmsResource> result = new ArrayList<CmsResource>(); 385 Map<String, List<CmsResource>> sortCategories = new HashMap<String, List<CmsResource>>(); 386 String foldername = null; 387 boolean includeSubTree = false; 388 if (data.getFileName() != null) { 389 foldername = CmsResource.getFolderPath(data.getFileName()); 390 includeSubTree = data.isSubTree(); 391 } else { 392 foldername = "/"; 393 includeSubTree = true; 394 } 395 396 CmsResourceFilter filter = CmsResourceFilter.DEFAULT.addExcludeFlags(CmsResource.FLAG_TEMPFILE); 397 if (data.getType() != -1) { 398 filter = filter.addRequireType(data.getType()); 399 } 400 if (data.isExcludeTimerange() && !cms.getRequestContext().getCurrentProject().isOnlineProject()) { 401 // include all not yet released and expired resources in an offline project 402 filter = filter.addExcludeTimerange(); 403 } 404 405 List<CmsResource> resources = cms.readResources(foldername, filter, includeSubTree); 406 List<String> categoryTypes = data.getCategoryTypes(); 407 Iterator<CmsResource> itResources = resources.iterator(); 408 CmsResource resource; 409 CmsCategoryService service = CmsCategoryService.getInstance(); 410 while (itResources.hasNext()) { 411 resource = itResources.next(); 412 Iterator<CmsCategory> itCategories = service.readResourceCategories(cms, resource).iterator(); 413 while (itCategories.hasNext()) { 414 CmsCategory category = itCategories.next(); 415 if (categoryTypes.contains(category.getPath())) { 416 if ((data.getSortBy() != null) && data.getSortBy().equals("category")) { 417 if (sortCategories.containsKey(category.getPath())) { 418 (sortCategories.get(category.getPath())).add(resource); 419 } else { 420 List<CmsResource> sortResources = new ArrayList<CmsResource>(); 421 sortResources.add(resource); 422 sortCategories.put(category.getPath(), sortResources); 423 } 424 } else { 425 if (!result.contains(resource)) { 426 result.add(resource); 427 } 428 } 429 } 430 } 431 } 432 433 if ((data.getSortBy() != null) && data.getSortBy().equals("date")) { 434 if (!data.isSortAsc()) { 435 Collections.sort(result, COMPARE_DATE_RELEASED_DESC); 436 } else { 437 Collections.sort(result, I_CmsResource.COMPARE_DATE_RELEASED); 438 } 439 } else if ((data.getSortBy() != null) && data.getSortBy().equals("category")) { 440 // categories are sort by their paths 441 Iterator<String> itCategoryTypes = categoryTypes.iterator(); 442 while (itCategoryTypes.hasNext()) { 443 List<CmsResource> categoryListToAdd = sortCategories.get(itCategoryTypes.next()); 444 if (categoryListToAdd != null) { 445 result.addAll(categoryListToAdd); 446 } 447 } 448 } else if ((data.getSortBy() != null) && data.getSortBy().equals("property")) { 449 Comparator<CmsResource> comp = new CmsPropertyResourceComparator( 450 cms, 451 data.getSortByPropertyName(), 452 data.getSortOrder()); 453 Collections.sort(result, comp); 454 } 455 456 return shrinkToFit(result, data.getCount(), numResults); 457 } 458 return null; 459 } 460}