DefaultCalendarViewController.java
- /*
- * #%L
- * *********************************************************************************************************************
- *
- * NorthernWind - lightweight CMS
- * http://northernwind.tidalwave.it - git clone https://bitbucket.org/tidalwave/northernwind-src.git
- * %%
- * Copyright (C) 2011 - 2023 Tidalwave s.a.s. (http://tidalwave.it)
- * %%
- * *********************************************************************************************************************
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- *
- * *********************************************************************************************************************
- *
- *
- * *********************************************************************************************************************
- * #L%
- */
- package it.tidalwave.northernwind.frontend.ui.component.calendar;
- import javax.annotation.Nonnegative;
- import javax.annotation.Nonnull;
- import java.util.List;
- import java.util.Map;
- import java.util.Optional;
- import java.util.SortedMap;
- import java.util.TreeMap;
- import java.util.stream.IntStream;
- import java.time.ZonedDateTime;
- import java.time.ZoneId;
- import it.tidalwave.util.TimeProvider;
- import it.tidalwave.northernwind.core.model.HttpStatusException;
- import it.tidalwave.northernwind.core.model.ResourcePath;
- import it.tidalwave.northernwind.core.model.RequestLocaleManager;
- import it.tidalwave.northernwind.core.model.ResourceProperties;
- import it.tidalwave.northernwind.core.model.SiteNode;
- import it.tidalwave.northernwind.frontend.ui.RenderContext;
- import it.tidalwave.northernwind.frontend.ui.component.calendar.spi.CalendarDao;
- import lombok.RequiredArgsConstructor;
- import lombok.ToString;
- import lombok.extern.slf4j.Slf4j;
- import static java.util.Collections.emptyMap;
- import static java.util.stream.Collectors.*;
- import static javax.servlet.http.HttpServletResponse.*;
- import static it.tidalwave.northernwind.core.model.Content.P_TITLE;
- /***********************************************************************************************************************
- *
- * <p>A default implementation of the {@link CalendarViewController} that is independent of the presentation technology.
- * This class is capable to render a yearly calendar with items and related links.</p>
- *
- * <p>It accepts a single path parameter {@code year} with selects a given year; otherwise the current year is used.</p>
- *
- * <p>Supported properties of the {@link SiteNode}:</p>
- *
- * <ul>
- * <li>{@code P_ENTRIES}: a property with XML format that describes the entries;</li>
- * <li>{@code P_SELECTED_YEAR}: the year to render (optional, otherwise the current year is used);</li>
- * <li>{@code P_FIRST_YEAR}: the first available year;</li>
- * <li>{@code P_LAST_YEAR}: the last available year ;</li>
- * <li>{@code P_TITLE}: the page title (optional);</li>
- * <li>{@code P_COLUMNS}: the number of columns of the table to render (optional, defaults to 4).</li>
- * </ul>
- *
- * <p>The property {@code P_ENTRIES} must have the following structure:</p>
- *
- * <pre>
- * <?xml version="1.0" encoding="UTF-8"?>
- * <calendar>
- * <year id="2004">
- * <month id="jan">
- * <item name="Provence" type="major" link="/diary/2004/01/02/"/>
- * <item name="Bocca di Magra" link="/diary/2004/01/24/"/>
- * <item name="Maremma" link="/diary/2004/01/31/"/>
- * </month>
- * ...
- * </year>
- * ...
- * </calendar>
- * </pre>
- *
- * <p>Concrete implementations must provide one method for rendering the calendar:</p>
- *
- * <ul>
- * <li>{@link #render(int, int, int, java.util.Map)}</li>
- * </ul>
- *
- * @author Fabrizio Giudici
- *
- **********************************************************************************************************************/
- @RequiredArgsConstructor @Slf4j
- public abstract class DefaultCalendarViewController implements CalendarViewController
- {
- @RequiredArgsConstructor @ToString
- public static class Entry
- {
- public final int month;
- public final String name;
- public final String link;
- public final Optional<String> type;
- }
- @Nonnull
- private final CalendarView view;
- @Nonnull
- private final SiteNode siteNode;
- @Nonnull
- protected final RequestLocaleManager requestLocaleManager;
- @Nonnull
- private final CalendarDao dao;
- @Nonnull
- private final TimeProvider timeProvider;
- private int year;
- private int firstYear;
- private int lastYear;
- private final SortedMap<Integer, List<Entry>> entriesByMonth = new TreeMap<>();
- /*******************************************************************************************************************
- *
- * Compute stuff here, to eventually fail fast.
- *
- * {@inheritDoc}
- *
- ******************************************************************************************************************/
- @Override
- public void prepareRendering (@Nonnull final RenderContext context)
- throws HttpStatusException
- {
- final int requestedYear = getRequestedYear(context.getPathParams(siteNode));
- final ResourceProperties siteNodeProperties = siteNode.getProperties();
- final ResourceProperties viewProperties = getViewProperties();
- year = viewProperties.getProperty(P_SELECTED_YEAR).orElse(requestedYear);
- firstYear = viewProperties.getProperty(P_FIRST_YEAR).orElse(Math.min(year, requestedYear));
- lastYear = viewProperties.getProperty(P_LAST_YEAR).orElse(getCurrentYear());
- log.info("prepareRendering() - {} f: {} l: {} r: {} y: {}", siteNode, firstYear, lastYear, requestedYear, year);
- if ((year < firstYear) || (year > lastYear))
- {
- throw new HttpStatusException(SC_NOT_FOUND);
- }
- entriesByMonth.putAll(siteNodeProperties.getProperty(P_ENTRIES).map(e -> findEntriesForYear(e, year))
- .orElse(emptyMap()));
- }
- /*******************************************************************************************************************
- *
- * {@inheritDoc}
- *
- ******************************************************************************************************************/
- @Override
- public void renderView (@Nonnull final RenderContext context)
- {
- render(siteNode.getProperty(P_TITLE), year, firstYear, lastYear, entriesByMonth, getViewProperties().getProperty(P_COLUMNS).orElse(4));
- }
- /*******************************************************************************************************************
- *
- * Renders the diary.
- *
- * @param title a title for the page (optional)
- * @param year the current year
- * @param firstYear the first available year
- * @param lastYear the last available year
- * @param byMonth a map of entries for the current year indexed by month
- * @param columns the number of columns of the table to render
- *
- ******************************************************************************************************************/
- protected abstract void render (@Nonnull final Optional<String> title,
- @Nonnegative final int year,
- @Nonnegative final int firstYear,
- @Nonnegative final int lastYear,
- @Nonnull final SortedMap<Integer, List<Entry>> byMonth,
- final int columns);
- /*******************************************************************************************************************
- *
- ******************************************************************************************************************/
- @Nonnull
- protected final ResourceProperties getViewProperties()
- {
- return siteNode.getPropertyGroup(view.getId());
- }
- /*******************************************************************************************************************
- *
- * Creates a link for the current year.
- *
- * @param year the year
- * @return the link
- *
- ******************************************************************************************************************/
- @Nonnull
- protected final String createYearLink (final int year)
- {
- return siteNode.getSite().createLink(siteNode.getRelativeUri().appendedWith(Integer.toString(year)));
- }
- /*******************************************************************************************************************
- *
- * Retrieves a map of entries for the given year, indexed by month.
- *
- * @param entries the configuration data
- * @param year the year
- * @return the map
- *
- ******************************************************************************************************************/
- @Nonnull
- private Map<Integer, List<Entry>> findEntriesForYear (@Nonnull final String entries, @Nonnegative final int year)
- {
- return IntStream.rangeClosed(1, 12).boxed()
- .flatMap(month -> dao.findMonthlyEntries(siteNode.getSite(), entries, month, year).stream())
- .collect(groupingBy(e -> e.month));
- }
- /*******************************************************************************************************************
- *
- * Returns the current year reading it from the path params, or by default from the calendar.
- *
- ******************************************************************************************************************/
- @Nonnegative
- private int getRequestedYear (@Nonnull final ResourcePath pathParams)
- throws HttpStatusException
- {
- if (pathParams.getSegmentCount() > 1)
- {
- throw new HttpStatusException(SC_BAD_REQUEST);
- }
- try
- {
- return pathParams.isEmpty() ? getCurrentYear() : Integer.parseInt(pathParams.getLeading());
- }
- catch (NumberFormatException e)
- {
- throw new HttpStatusException(SC_BAD_REQUEST);
- }
- }
- /*******************************************************************************************************************
- *
- ******************************************************************************************************************/
- @Nonnegative
- private int getCurrentYear()
- {
- return ZonedDateTime.ofInstant(timeProvider.get(), ZoneId.of("UTC")).getYear();
- }
- }