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, 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.jsp.util; 029 030import org.opencms.acacia.shared.I_CmsSerialDateValue; 031import org.opencms.acacia.shared.I_CmsSerialDateValue.DateType; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsResource; 034import org.opencms.main.CmsException; 035import org.opencms.main.CmsLog; 036import org.opencms.search.galleries.CmsGallerySearch; 037import org.opencms.search.galleries.CmsGallerySearchResult; 038import org.opencms.util.CmsCollectionsGenericWrapper; 039import org.opencms.widgets.serialdate.CmsSerialDateBeanFactory; 040import org.opencms.widgets.serialdate.CmsSerialDateValue; 041import org.opencms.widgets.serialdate.I_CmsSerialDateBean; 042 043import java.util.ArrayList; 044import java.util.Calendar; 045import java.util.Date; 046import java.util.GregorianCalendar; 047import java.util.List; 048import java.util.Locale; 049import java.util.Map; 050import java.util.SortedSet; 051import java.util.TreeSet; 052 053import org.apache.commons.collections.Transformer; 054import org.apache.commons.logging.Log; 055 056import com.google.common.base.Objects; 057 058/** Bean for easy access to information of an event series. */ 059public class CmsJspDateSeriesBean { 060 061 /** 062 * Provides information on the single event when the start date is provided.<p> 063 * 064 * If no valid start date is provided, the information for the first event in the series is provided. 065 */ 066 public class CmsSeriesSingleEventTransformer implements Transformer { 067 068 /** 069 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 070 */ 071 public Object transform(Object date) { 072 073 Date d = toDate(date); 074 if ((null != d) && m_dates.contains(d)) { 075 return new CmsJspInstanceDateBean((Date)d.clone(), CmsJspDateSeriesBean.this); 076 } else { 077 if (!m_dates.isEmpty()) { 078 return getFirst(); 079 } 080 } 081 return null; 082 } 083 } 084 085 /** Logger for the class. */ 086 private static final Log LOG = CmsLog.getLog(CmsJspDateSeriesBean.class); 087 088 /** The duration of a single event. */ 089 private Long m_duration; 090 091 /** The first event of the series. */ 092 private CmsJspInstanceDateBean m_firstEvent; 093 094 /** Flag, indicating if the single events last over night. */ 095 private Boolean m_isMultiDay; 096 097 /** The last event of the series. */ 098 private CmsJspInstanceDateBean m_lastEvent; 099 100 /** The locale to use for displaying dates. */ 101 private Locale m_locale; 102 103 /** The parent series. */ 104 private CmsJspDateSeriesBean m_parentSeries; 105 106 /** The series definition. */ 107 private I_CmsSerialDateValue m_seriesDefinition; 108 109 /** Lazy map from start dates (provided as Long, long value as string or date) to informations on the single event. */ 110 private Map<Object, CmsJspInstanceDateBean> m_singleEvents; 111 112 /** The content value containing the series definition. */ 113 private CmsJspContentAccessValueWrapper m_value; 114 115 /** The dates of the series. */ 116 SortedSet<Date> m_dates; 117 118 /** 119 * Constructor for a date series bean.<p> 120 * 121 * @param value the content value wrapper for the element that stores the series definition 122 * @param locale the locale in which dates should be rendered. This can differ from the content locale, if e.g. 123 * on a German page a content that is only present in English is rendered. 124 */ 125 public CmsJspDateSeriesBean(CmsJspContentAccessValueWrapper value, Locale locale) { 126 127 m_value = value; 128 m_locale = null == locale ? m_value.getLocale() : locale; 129 initFromSeriesDefinition(value.toString()); 130 } 131 132 /** 133 * Constructor for the date series bean for testing purposes where no OpenCms object is required.<p> 134 * 135 * NOTE: The parent series cannot be determined if this constructor is used.<p> 136 * 137 * @param seriesDefinition the series definition. 138 * @param locale the locale in which dates should be rendered. This can differ from the content locale, if e.g. 139 * on a German page a content that is only present in English is rendered. 140 */ 141 CmsJspDateSeriesBean(String seriesDefinition, Locale locale) { 142 143 m_locale = locale; 144 initFromSeriesDefinition(seriesDefinition); 145 } 146 147 /** 148 * Returns the list of start dates for all instances of the series.<p> 149 * 150 * @return the list of start dates for all instances of the series 151 */ 152 public List<Date> getDates() { 153 154 return new ArrayList<>(m_dates); 155 } 156 157 /** 158 * Returns the first event of this series.<p> 159 * 160 * In case this is just a single event and not a series, this is identical to the date of the event.<p> 161 * 162 * @return the first event of this series 163 */ 164 public CmsJspInstanceDateBean getFirst() { 165 166 if ((m_firstEvent == null) && (m_dates != null) && (!m_dates.isEmpty())) { 167 m_firstEvent = new CmsJspInstanceDateBean((Date)m_dates.first().clone(), CmsJspDateSeriesBean.this); 168 } 169 return m_firstEvent; 170 } 171 172 /** 173 * Returns the duration of a single instance in milliseconds.<p> 174 * 175 * @return the duration of a single instance in milliseconds 176 */ 177 public Long getInstanceDuration() { 178 179 return m_duration; 180 } 181 182 /** 183 * Returns a lazy map from the start time of a single instance of the series to the date information on the single instance.<p> 184 * 185 * Start time can be provided as Long, as a String representation of the long value or as Date.<p> 186 * 187 * If no event exists for the start time, the information for the first event of the series is returned. 188 * 189 * @return a lazy map from the start time of a single instance of the series to the date information on the single instance. 190 */ 191 public Map<Object, CmsJspInstanceDateBean> getInstanceInfo() { 192 193 if (m_singleEvents == null) { 194 m_singleEvents = CmsCollectionsGenericWrapper.createLazyMap(new CmsSeriesSingleEventTransformer()); 195 } 196 return m_singleEvents; 197 } 198 199 /** 200 * Returns a flag, indicating if the series is extracted from another series.<p> 201 * 202 * @return a flag, indicating if the series is extracted from another series 203 */ 204 public boolean getIsExtractedDate() { 205 206 return Objects.equal(m_seriesDefinition.getDateType(), DateType.EXTRACTED); 207 } 208 209 /** 210 * Returns a flag, indicating if the series is defined via a pattern, i.e., not just as via single date.<p> 211 * 212 * @return a flag, indicating if the series is defined via a pattern, i.e., not just as via single date 213 */ 214 public boolean getIsSeries() { 215 216 return Objects.equal(m_seriesDefinition.getDateType(), DateType.SERIES); 217 } 218 219 /** 220 * Returns a flag, indicating if the series is defined by only a single date and not extracted from another series.<p> 221 * 222 * @return a flag, indicating if the series is defined by only a single date and not extracted from another series 223 */ 224 public boolean getIsSingleDate() { 225 226 return Objects.equal(m_seriesDefinition.getDateType(), DateType.SINGLE); 227 } 228 229 /** 230 * Returns the last event of this series.<p> 231 * 232 * In case this is just a single event and not a series, this is identical to the date of the event.<p> 233 * 234 * @return the last event of this series 235 */ 236 public CmsJspInstanceDateBean getLast() { 237 238 if ((m_lastEvent == null) && (m_dates != null) && (!m_dates.isEmpty())) { 239 m_lastEvent = new CmsJspInstanceDateBean((Date)m_dates.last().clone(), CmsJspDateSeriesBean.this); 240 } 241 return m_lastEvent; 242 } 243 244 /** 245 * Returns the locale to use for rendering dates.<p> 246 * 247 * @return the locale to use for rendering dates 248 */ 249 public Locale getLocale() { 250 251 return m_locale; 252 } 253 254 /** 255 * Returns the next event of this series relative to the current date.<p> 256 * 257 * In case this is just a single event and not a series, this is identical to the date of the event.<p> 258 * 259 * @return the next event of this series 260 */ 261 public CmsJspInstanceDateBean getNext() { 262 263 return getNextFor(new Date()); 264 } 265 266 /** 267 * Returns the next event of this series that takes place at the given date or after it.<p> 268 * 269 * In case this is just a single event and not a series, this is identical to the date of the event.<p> 270 * 271 *@param date the date relative to which the event is returned. 272 * 273 * @return the next event of this series 274 */ 275 public CmsJspInstanceDateBean getNextFor(Object date) { 276 277 Date d = toDate(date); 278 if ((d != null) && (m_dates != null) && (!m_dates.isEmpty())) { 279 for (Date instanceDate : m_dates) { 280 if (!instanceDate.before(d)) { 281 return new CmsJspInstanceDateBean((Date)instanceDate.clone(), CmsJspDateSeriesBean.this); 282 } 283 } 284 } 285 return getLast(); 286 } 287 288 /** 289 * Returns the parent series, if it is present, otherwise <code>null</code>.<p> 290 * 291 * @return the parent series, if it is present, otherwise <code>null</code> 292 */ 293 public CmsJspDateSeriesBean getParentSeries() { 294 295 if ((m_parentSeries == null) && getIsExtractedDate()) { 296 CmsObject cms = m_value.getCmsObject(); 297 try { 298 CmsResource res = cms.readResource(m_seriesDefinition.getParentSeriesId()); 299 CmsJspContentAccessBean content = new CmsJspContentAccessBean(cms, m_value.getLocale(), res); 300 CmsJspContentAccessValueWrapper value = content.getValue().get(m_value.getPath()); 301 return new CmsJspDateSeriesBean(value, m_locale); 302 } catch (NullPointerException | CmsException e) { 303 LOG.warn("Parent series with id " + m_seriesDefinition.getParentSeriesId() + " could not be read.", e); 304 } 305 306 } 307 return null; 308 } 309 310 /** 311 * Returns the next event of this series relative to the current date.<p> 312 * 313 * In case this is just a single event and not a series, this is identical to the date of the event.<p> 314 * 315 * @return the next event of this series 316 */ 317 public CmsJspInstanceDateBean getPrevious() { 318 319 return getPreviousFor(new Date()); 320 } 321 322 /** 323 * Returns the next event of this series that takes place at the given date or after it.<p> 324 * 325 * In case this is just a single event and not a series, this is identical to the date of the event.<p> 326 * 327 *@param date the date relative to which the event is returned. 328 * 329 * @return the next event of this series 330 */ 331 public CmsJspInstanceDateBean getPreviousFor(Object date) { 332 333 Date d = toDate(date); 334 if ((d != null) && (m_dates != null) && (!m_dates.isEmpty())) { 335 Date lastDate = m_dates.first(); 336 for (Date instanceDate : m_dates) { 337 if (instanceDate.after(d)) { 338 return new CmsJspInstanceDateBean((Date)lastDate.clone(), CmsJspDateSeriesBean.this); 339 } 340 lastDate = instanceDate; 341 } 342 return new CmsJspInstanceDateBean((Date)lastDate.clone(), CmsJspDateSeriesBean.this); 343 } 344 return getLast(); 345 } 346 347 /** 348 * Returns the gallery title of the series content.<p> 349 * 350 * @return the gallery title of the series content 351 */ 352 public String getTitle() { 353 354 CmsGallerySearchResult result; 355 try { 356 result = CmsGallerySearch.searchById( 357 m_value.getCmsObject(), 358 m_value.getContentValue().getDocument().getFile().getStructureId(), 359 m_value.getLocale()); 360 return result.getTitle(); 361 } catch (CmsException e) { 362 LOG.warn("Could not retrieve title of series content.", e); 363 return ""; 364 } 365 366 } 367 368 /** 369 * Returns a flag, indicating if the single events last over night.<p> 370 * 371 * @return <code>true</code> if the event ends on another day than it starts, <code>false</code> if it ends on the same day 372 */ 373 public boolean isMultiDay() { 374 375 if (m_isMultiDay != null) { 376 return m_isMultiDay.booleanValue(); 377 } 378 379 Long instanceDuration = getInstanceDuration(); 380 381 if ((null == instanceDuration) || (instanceDuration.longValue() == 0)) { 382 m_isMultiDay = Boolean.FALSE; 383 return false; 384 } 385 if (getInstanceDuration().longValue() > I_CmsSerialDateValue.DAY_IN_MILLIS) { 386 m_isMultiDay = Boolean.TRUE; 387 return true; 388 } 389 if (isWholeDay() && (getInstanceDuration().longValue() <= I_CmsSerialDateValue.DAY_IN_MILLIS)) { 390 m_isMultiDay = Boolean.FALSE; 391 return false; 392 } 393 Calendar start = new GregorianCalendar(); 394 start.setTime(m_seriesDefinition.getStart()); 395 Calendar end = new GregorianCalendar(); 396 end.setTime(m_seriesDefinition.getEnd()); 397 if (start.get(Calendar.DAY_OF_MONTH) == end.get(Calendar.DAY_OF_MONTH)) { 398 m_isMultiDay = Boolean.FALSE; 399 return false; 400 } 401 m_isMultiDay = Boolean.TRUE; 402 return true; 403 } 404 405 /** 406 * Returns a flag, indicating if the events in the series last whole days.<p> 407 * 408 * @return a flag, indicating if the events in the series last whole days 409 */ 410 public boolean isWholeDay() { 411 412 return m_seriesDefinition.isWholeDay(); 413 } 414 415 /** 416 * Converts the provided object to a date, if possible. 417 * 418 * @param date the date. 419 * 420 * @return the date as {@link java.util.Date} 421 */ 422 public Date toDate(Object date) { 423 424 Date d = null; 425 if (null != date) { 426 if (date instanceof Date) { 427 d = (Date)date; 428 } else if (date instanceof Long) { 429 d = new Date(((Long)date).longValue()); 430 } else { 431 try { 432 long l = Long.parseLong(date.toString()); 433 d = new Date(l); 434 } catch (Exception e) { 435 // do nothing, just let d remain null 436 } 437 } 438 } 439 return d; 440 } 441 442 /** 443 * Initialization based on the series definition.<p> 444 * 445 * @param seriesDefinition the series definition 446 */ 447 private void initFromSeriesDefinition(String seriesDefinition) { 448 449 String seriesDefinitionString = seriesDefinition; 450 m_seriesDefinition = new CmsSerialDateValue(seriesDefinitionString); 451 if (m_seriesDefinition.isValid()) { 452 I_CmsSerialDateBean bean = CmsSerialDateBeanFactory.createSerialDateBean(m_seriesDefinition); 453 m_dates = bean.getDates(); 454 m_duration = bean.getEventDuration(); 455 } else { 456 try { 457 throw new Exception("Could not read series definition: " + seriesDefinitionString); 458 } catch (Exception e) { 459 LOG.debug(e.getMessage(), e); 460 } 461 m_dates = new TreeSet<>(); 462 } 463 } 464}