Skip to content

Method: findEntriesForYear(String, int)

1: /*
2: * *************************************************************************************************************************************************************
3: *
4: * NorthernWind - lightweight CMS
5: * http://tidalwave.it/projects/northernwind
6: *
7: * Copyright (C) 2011 - 2025 Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *************************************************************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12: * You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * 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
17: * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
18: *
19: * *************************************************************************************************************************************************************
20: *
21: * git clone https://bitbucket.org/tidalwave/northernwind-src
22: * git clone https://github.com/tidalwave-it/northernwind-src
23: *
24: * *************************************************************************************************************************************************************
25: */
26: package it.tidalwave.northernwind.frontend.ui.component.calendar;
27:
28: import javax.annotation.Nonnegative;
29: import javax.annotation.Nonnull;
30: import java.time.ZoneId;
31: import java.time.ZonedDateTime;
32: import java.util.List;
33: import java.util.Map;
34: import java.util.Optional;
35: import java.util.SortedMap;
36: import java.util.TreeMap;
37: import java.util.stream.IntStream;
38: import it.tidalwave.util.TimeProvider;
39: import it.tidalwave.northernwind.core.model.HttpStatusException;
40: import it.tidalwave.northernwind.core.model.RequestLocaleManager;
41: import it.tidalwave.northernwind.core.model.ResourcePath;
42: import it.tidalwave.northernwind.core.model.ResourceProperties;
43: import it.tidalwave.northernwind.core.model.SiteNode;
44: import it.tidalwave.northernwind.frontend.ui.RenderContext;
45: import it.tidalwave.northernwind.frontend.ui.component.calendar.spi.CalendarDao;
46: import lombok.RequiredArgsConstructor;
47: import lombok.ToString;
48: import lombok.extern.slf4j.Slf4j;
49: import static java.util.Collections.emptyMap;
50: import static java.util.stream.Collectors.*;
51: import static javax.servlet.http.HttpServletResponse.*;
52: import static it.tidalwave.northernwind.core.model.Content.P_TITLE;
53:
54: /***************************************************************************************************************************************************************
55: *
56: * <p>A default implementation of the {@link CalendarViewController} that is independent of the presentation technology.
57: * This class is capable to render a yearly calendar with items and related links.</p>
58: *
59: * <p>It accepts a single path parameter {@code year} with selects a given year; otherwise the current year is used.</p>
60: *
61: * <p>Supported properties of the {@link SiteNode}:</p>
62: *
63: * <ul>
64: * <li>{@code P_ENTRIES}: a property with XML format that describes the entries;</li>
65: * <li>{@code P_SELECTED_YEAR}: the year to render (optional, otherwise the current year is used);</li>
66: * <li>{@code P_FIRST_YEAR}: the first available year;</li>
67: * <li>{@code P_LAST_YEAR}: the last available year ;</li>
68: * <li>{@code P_TITLE}: the page title (optional);</li>
69: * <li>{@code P_COLUMNS}: the number of columns of the table to render (optional, defaults to 4).</li>
70: * </ul>
71: *
72: * <p>The property {@code P_ENTRIES} must have the following structure:</p>
73: *
74: * <pre>
75: * <?xml version="1.0" encoding="UTF-8"?>
76: * <calendar>
77: * <year id="2004">
78: * <month id="jan">
79: * <item name="Provence" type="major" link="/diary/2004/01/02/"/>
80: * <item name="Bocca di Magra" link="/diary/2004/01/24/"/>
81: * <item name="Maremma" link="/diary/2004/01/31/"/>
82: * </month>
83: * ...
84: * </year>
85: * ...
86: * </calendar>
87: * </pre>
88: *
89: * <p>Concrete implementations must provide one method for rendering the calendar:</p>
90: *
91: * <ul>
92: * <li>{@link #render(int, int, int, java.util.Map)}</li>
93: * </ul>
94: *
95: * @author Fabrizio Giudici
96: *
97: **************************************************************************************************************************************************************/
98: @RequiredArgsConstructor @Slf4j
99: public abstract class DefaultCalendarViewController implements CalendarViewController
100: {
101: @RequiredArgsConstructor @ToString
102: public static class Entry
103: {
104: public final int month;
105: public final String name;
106: public final String link;
107: public final Optional<String> type;
108: }
109:
110: @Nonnull
111: private final CalendarView view;
112:
113: @Nonnull
114: private final SiteNode siteNode;
115:
116: @Nonnull
117: protected final RequestLocaleManager requestLocaleManager;
118:
119: @Nonnull
120: private final CalendarDao dao;
121:
122: @Nonnull
123: private final TimeProvider timeProvider;
124:
125: private int year;
126:
127: private int firstYear;
128:
129: private int lastYear;
130:
131: private final SortedMap<Integer, List<Entry>> entriesByMonth = new TreeMap<>();
132:
133: /***********************************************************************************************************************************************************
134: * Compute stuff here, to eventually fail fast.
135: *
136: * {@inheritDoc}
137: **********************************************************************************************************************************************************/
138: @Override
139: public void prepareRendering (@Nonnull final RenderContext context)
140: throws HttpStatusException
141: {
142: final var requestedYear = getRequestedYear(context.getPathParams(siteNode));
143: final var siteNodeProperties = siteNode.getProperties();
144: final var viewProperties = getViewProperties();
145:
146: year = viewProperties.getProperty(P_SELECTED_YEAR).orElse(requestedYear);
147: firstYear = viewProperties.getProperty(P_FIRST_YEAR).orElse(Math.min(year, requestedYear));
148: lastYear = viewProperties.getProperty(P_LAST_YEAR).orElse(getCurrentYear());
149: log.info("prepareRendering() - {} f: {} l: {} r: {} y: {}", siteNode, firstYear, lastYear, requestedYear, year);
150:
151: if ((year < firstYear) || (year > lastYear))
152: {
153: throw new HttpStatusException(SC_NOT_FOUND);
154: }
155:
156: entriesByMonth.putAll(siteNodeProperties.getProperty(P_ENTRIES).map(e -> findEntriesForYear(e, year))
157: .orElse(emptyMap()));
158: }
159:
160: /***********************************************************************************************************************************************************
161: * {@inheritDoc}
162: **********************************************************************************************************************************************************/
163: @Override
164: public void renderView (@Nonnull final RenderContext context)
165: {
166: render(siteNode.getProperty(P_TITLE), year, firstYear, lastYear, entriesByMonth, getViewProperties().getProperty(P_COLUMNS).orElse(4));
167: }
168:
169: /***********************************************************************************************************************************************************
170: * Renders the diary.
171: *
172: * @param title a title for the page (optional)
173: * @param year the current year
174: * @param firstYear the first available year
175: * @param lastYear the last available year
176: * @param byMonth a map of entries for the current year indexed by month
177: * @param columns the number of columns of the table to render
178: **********************************************************************************************************************************************************/
179: protected abstract void render (@Nonnull final Optional<String> title,
180: @Nonnegative final int year,
181: @Nonnegative final int firstYear,
182: @Nonnegative final int lastYear,
183: @Nonnull final SortedMap<Integer, List<Entry>> byMonth,
184: final int columns);
185:
186: /***********************************************************************************************************************************************************
187: *
188: **********************************************************************************************************************************************************/
189: @Nonnull
190: protected final ResourceProperties getViewProperties()
191: {
192: return siteNode.getPropertyGroup(view.getId());
193: }
194:
195: /***********************************************************************************************************************************************************
196: * Creates a link for the current year.
197: *
198: * @param year the year
199: * @return the link
200: **********************************************************************************************************************************************************/
201: @Nonnull
202: protected final String createYearLink (final int year)
203: {
204: return siteNode.getSite().createLink(siteNode.getRelativeUri().appendedWith(Integer.toString(year)));
205: }
206:
207: /***********************************************************************************************************************************************************
208: * Retrieves a map of entries for the given year, indexed by month.
209: *
210: * @param entries the configuration data
211: * @param year the year
212: * @return the map
213: **********************************************************************************************************************************************************/
214: @Nonnull
215: private Map<Integer, List<Entry>> findEntriesForYear (@Nonnull final String entries, @Nonnegative final int year)
216: {
217: return IntStream.rangeClosed(1, 12).boxed()
218: .flatMap(month -> dao.findMonthlyEntries(siteNode.getSite(), entries, month, year).stream())
219: .collect(groupingBy(e -> e.month));
220: }
221:
222: /***********************************************************************************************************************************************************
223: * Returns the current year reading it from the path params, or by default from the calendar.
224: **********************************************************************************************************************************************************/
225: @Nonnegative
226: private int getRequestedYear (@Nonnull final ResourcePath pathParams)
227: throws HttpStatusException
228: {
229: if (pathParams.getSegmentCount() > 1)
230: {
231: throw new HttpStatusException(SC_BAD_REQUEST);
232: }
233:
234: try
235: {
236: return pathParams.isEmpty() ? getCurrentYear() : Integer.parseInt(pathParams.getLeading());
237: }
238: catch (NumberFormatException e)
239: {
240: throw new HttpStatusException(SC_BAD_REQUEST);
241: }
242: }
243:
244: /***********************************************************************************************************************************************************
245: *
246: **********************************************************************************************************************************************************/
247: @Nonnegative
248: private int getCurrentYear()
249: {
250: return ZonedDateTime.ofInstant(timeProvider.get(), ZoneId.of("UTC")).getYear();
251: }
252: }