Java tutorial
/** * Copyright 2010 Vstra Gtalandsregionen * * This library is free software; you can redistribute it and/or modify * it under the terms of version 2.1 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA * */ package se.vgregion.portal.notes.calendar.controllers; import com.google.api.client.auth.oauth2.TokenResponseException; import com.google.api.services.calendar.Calendar; import com.google.api.services.calendar.model.CalendarList; import com.google.api.services.calendar.model.CalendarListEntry; //import com.google.api.services.oauth2.model.Userinfo; import org.joda.time.DateTime; import org.joda.time.DateTimeConstants; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.portlet.ModelAndView; import org.springframework.web.portlet.bind.annotation.ActionMapping; import org.springframework.web.portlet.bind.annotation.RenderMapping; import org.springframework.web.portlet.context.PortletConfigAware; import se.vgregion.core.domain.calendar.CalendarEvents; import se.vgregion.core.domain.calendar.CalendarEventsPeriod; import se.vgregion.core.domain.calendar.CalendarItem; import se.vgregion.portal.calendar.util.EncodingUtil; import se.vgregion.services.calendar.CalendarService; import se.vgregion.services.calendar.CalendarServiceException; import se.vgregion.services.calendar.google.GoogleCalendarService; import se.vgregion.services.calendar.google.GoogleCalendarServiceException; import javax.portlet.*; import java.io.IOException; import java.io.Serializable; import java.util.*; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @Controller @SessionAttributes("displayPeriod") @RequestMapping("VIEW") @SuppressWarnings("unchecked") public class NotesCalendarViewController implements PortletConfigAware { private static final String TIME_FORMAT = "dd MMMM"; private static final String SELECTED_GOOGLE_CALENDARS = "selectedGoogleCalendars"; protected String displayPeriodKey = "displayPeriod"; /** * The name of the view page to dispatch to on a render request. */ protected static final String VIEW = "view"; public static final String NO_CALENDAR_VIEW = "no_calendar_view"; private static final Logger LOGGER = LoggerFactory.getLogger(NotesCalendarViewController.class); private CalendarService calendarService; private PortletConfig portletConfig = null; protected PortletData portletData = null; private GoogleCalendarService googleCalendarService; private Random random = new Random(); private Locale locale = new Locale("sv", "SE"); /** * Constructs a NotesCalendarViewController. * * @param calendarService a calendarService */ @Autowired public NotesCalendarViewController(CalendarService calendarService, GoogleCalendarService googleCalendarService) { this.calendarService = calendarService; this.googleCalendarService = googleCalendarService; } public void setPortletConfig(PortletConfig portletConfig) { this.portletConfig = portletConfig; } @Autowired public void setPortletData(PortletData portletData) { this.portletData = portletData; } /** * Displays the calendar events for the logged in user. * * @param model the model * @param request the portletRequest * @param response the portletResponse * @return the view to display */ @RenderMapping public String displayCalendarEvents(ModelMap model, RenderRequest request, RenderResponse response) { // It would seem that Joda Times DateTimeZone does not use Sun:s TimeZone.getDefault (user.timezone) as its // often claims but defaults to timezone UTC. So therefor we do this: DateTimeZone.setDefault(DateTimeZone.forTimeZone(TimeZone.getDefault())); // ... getting the value set in the java_opt - setting it as default for JT. String userId = portletData.getUserId(request); LOGGER.debug("Userid: {}", userId); CalendarEventsPeriod displayPeriod = (CalendarEventsPeriod) model.get(displayPeriodKey); if (displayPeriod == null) { DateTime startDate = new DateTime().withDayOfWeek(DateTimeConstants.MONDAY).withHourOfDay(0) .withMinuteOfHour(0).withSecondOfMinute(0).withMillisOfSecond(0); displayPeriod = new CalendarEventsPeriod(startDate, CalendarEventsPeriod.DEFAULT_PERIOD_LENGTH); model.put(displayPeriodKey, displayPeriod); } try { Map<String, Future<CalendarEvents>> futureCalendarEvents = new HashMap<String, Future<CalendarEvents>>(); // Initialize CalendarEvents CalendarEvents events = new CalendarEvents(); events.setCalendarItems(new ArrayList<CalendarItem>()); // Retrieve asynchronously futureCalendarEvents.put("VGR", calendarService.getFutureCalendarEvents(userId, displayPeriod)); // Get from Google, asynchronously // String selectedCalendars = request.getPreferences().getValue(this.SELECTED_GOOGLE_CALENDARS, ""); // List<String> selectedCalendarsList = Arrays.asList(stringToArray(selectedCalendars)); // futureCalendarEvents.put("Google", googleCalendarService.getFutureCalendarEvents(userId, displayPeriod, // selectedCalendarsList)); // Get from other sources, asynchronously. Map<String, String> externalSources = getExternalSources(request.getPreferences()); for (Map.Entry<String, String> externalSource : externalSources.entrySet()) { futureCalendarEvents.put(externalSource.getKey(), calendarService.getFutureCalendarEventsFromIcalUrl(externalSource.getValue(), displayPeriod, externalSource.getKey())); } // Now that we have a list of Future objects which all are processed concurrently we start to "get()" them. List<String> failedRetrievals = new ArrayList<String>(); for (Map.Entry<String, Future<CalendarEvents>> futureCalendarEvent : futureCalendarEvents.entrySet()) { try { Future<CalendarEvents> value = futureCalendarEvent.getValue(); CalendarEvents calendarEvents = value.get(15, TimeUnit.SECONDS); if (calendarEvents != null) { List<CalendarItem> calendarItems = calendarEvents.getCalendarItems(); if (calendarItems != null) { events.getCalendarItems().addAll(calendarItems); } } } catch (Exception ex) { if (userId.equals("lifra1")) { LOGGER.warn("Failed to get a calendar for user " + userId + ". " + ex.getMessage()); } else { LOGGER.warn("Failed to get a calendar for user " + userId + ". " + ex.getMessage(), ex); } failedRetrievals.add(futureCalendarEvent.getKey()); } } if (failedRetrievals.size() > 0) { String errorMessage = "Fljande hmtningar misslyckades: " + StringUtils.arrayToCommaDelimitedString(failedRetrievals.toArray()) + "."; model.addAttribute("errorMessage", errorMessage); } List<List<CalendarItem>> calendarItems = events.getCalendarItemsGroupedByStartDate(); model.put("displayPeriodText", getFormattedDateIntervalToTitle(displayPeriod, locale)); model.put("calendarItems", calendarItems); model.put("randomNumber", random.nextInt()); return VIEW; } catch (RuntimeException ex) { LOGGER.error(ex.getMessage(), ex); return NO_CALENDAR_VIEW; } catch (Exception ex) { LOGGER.error(ex.getMessage(), ex); return NO_CALENDAR_VIEW; } } /** * Displays the editExternalSources view. * * @param request the request * @param response the response * @param model the model * @return the view to display * @throws ClassNotFoundException ClassNotFoundException * @throws IOException IOException */ @RenderMapping(params = "action=editExternalSources") public String editExternalSources(RenderRequest request, RenderResponse response, Model model) throws ClassNotFoundException, IOException { PortletPreferences preferences = request.getPreferences(); /*String userId = lookupP3PInfo(request, PortletRequest.P3PUserInfos.USER_LOGIN_ID); Userinfo userinfo = googleCalendarService.getUserinfo(userId); if (userinfo != null) { model.addAttribute("googleEmail", userinfo.getEmail()); }*/ Map<String, String> externalSources = getExternalSources(preferences); model.addAttribute("externalSources", externalSources); return "editExternalSources"; } @RenderMapping(params = "action=editGoogleCalendar") public String editGoogleCalendar(RenderRequest request, RenderResponse response, Model model) { String userId = lookupP3PInfo(request, PortletRequest.P3PUserInfos.USER_LOGIN_ID); /* Calendar calendar = googleCalendarService.getCalendar(userId); if (calendar != null) { try { CalendarList calendarList = calendar.calendarList().list().execute(); List<CalendarListEntry> calendarListEntries = calendarList.getItems(); model.addAttribute("calendarListEntries", calendarListEntries); Userinfo userinfo = googleCalendarService.getUserinfo(userId); model.addAttribute("googleEmail", userinfo.getEmail()); } catch (IOException e) { LOGGER.error(e.getMessage(), e); } } */ String selectedCalendarsString = request.getPreferences().getValue(SELECTED_GOOGLE_CALENDARS, null); String[] selectedCalendars = stringToArray(selectedCalendarsString); model.addAttribute(this.SELECTED_GOOGLE_CALENDARS, selectedCalendars); return "editGoogleCalendar"; } @ActionMapping(params = "action=addGoogleCalendar") public void addGoogleCalendar(ActionRequest request, ActionResponse response, Model model) throws IOException { String userId = lookupP3PInfo(request, PortletRequest.P3PUserInfos.USER_LOGIN_ID); /* if (!googleCalendarService.isAuthorized(userId)) { response.sendRedirect(googleCalendarService.getRedirectUrl()); } else { response.setRenderParameter("action", "editGoogleCalendar"); }*/ } @RenderMapping(params = "action=googleCallback") public String googleCallback(RenderRequest request, RenderResponse response, Model model) throws IOException { String authorizationCode = request.getParameter("code"); String userId = lookupP3PInfo(request, PortletRequest.P3PUserInfos.USER_LOGIN_ID); /* try { googleCalendarService.authorize(authorizationCode, userId); } catch (TokenResponseException e) { LOGGER.warn(e.getMessage()); }*/ return editGoogleCalendar(request, response, model); } @ActionMapping(params = "action=saveGoogleCalendar") public void saveGoogleCalendar(ActionRequest request, ActionResponse response) throws ReadOnlyException, ValidatorException, IOException { String[] selectedCalendarsArray = request.getParameterValues(SELECTED_GOOGLE_CALENDARS); String selectedCalendars = arrayToString(selectedCalendarsArray); PortletPreferences preferences = request.getPreferences(); preferences.setValue(this.SELECTED_GOOGLE_CALENDARS, selectedCalendars); preferences.store(); response.setRenderParameter("action", "editExternalSources"); } @ActionMapping(params = "action=removeGoogleCalendar") public void removeGoogleCalendar(ActionRequest request, ActionResponse response, Model model) throws GoogleCalendarServiceException { String userId = lookupP3PInfo(request, PortletRequest.P3PUserInfos.USER_LOGIN_ID); //googleCalendarService.resetAuthorization(userId); response.setRenderParameter("action", "editExternalSources"); } String arrayToString(String[] array) { if (array == null || array.length == 0) { return ""; } final String separator = "==SEPARATOR=="; StringBuilder sb = new StringBuilder(); for (String s : array) { sb.append(separator + s); } sb.delete(0, separator.length()); return sb.toString(); } String[] stringToArray(String string) { if (string == null) { return new String[0]; } return string.split("==SEPARATOR=="); } protected String lookupP3PInfo(PortletRequest req, PortletRequest.P3PUserInfos p3pInfo) { Map<String, String> userInfo = (Map<String, String>) req.getAttribute(PortletRequest.USER_INFO); String info; if (userInfo != null) { info = userInfo.get(p3pInfo.toString()); } else { return null; } return info; } protected Map<String, String> decodeExternalSources(String externalSourcesEncoded) throws IOException, ClassNotFoundException { Map<String, String> externalSources; try { externalSources = EncodingUtil.decodeToObject(externalSourcesEncoded); } catch (RuntimeException ex) { LOGGER.error(ex.getMessage(), ex); externalSources = new HashMap<String, String>(); } return externalSources; } /** * Action to either add, update or remove an external calendar source. * * @param request the request * @param response the response * @param model the model * @throws IOException IOException * @throws ClassNotFoundException ClassNotFoundException * @throws ReadOnlyException ReadOnlyException * @throws ValidatorException ValidatorException */ @ActionMapping(params = "action=editExternalSource") public void editExternalSource(ActionRequest request, ActionResponse response, Model model) throws IOException, ClassNotFoundException, ReadOnlyException, ValidatorException { String externalSourceKey = request.getParameter("externalSourceKey"); String externalSourceUrl = request.getParameter("externalSourceUrl"); String action = request.getParameter("submitType"); if (!"Radera".equals(action)) { try { calendarService.validateAsValidIcalUrl(externalSourceUrl); } catch (CalendarServiceException e) { LOGGER.info("The provided URL could not be parsed.", e); model.addAttribute("errorMessage", "URL:en \"" + externalSourceUrl + "\" gick inte att lsa."); response.setRenderParameter("action", "editExternalSources"); return; } } PortletPreferences preferences = request.getPreferences(); Map<String, String> externalSources = getExternalSources(preferences); if ("Uppdatera".equals(action)) { String oldExternalSourceKey = request.getParameter("oldExternalSourceKey"); if (!externalSourceKey.equals(oldExternalSourceKey)) { externalSources.remove(oldExternalSourceKey); } externalSources.put(externalSourceKey, externalSourceUrl); } else if ("Radera".equals(action)) { String oldExternalSourceKey = request.getParameter("oldExternalSourceKey"); externalSources.remove(oldExternalSourceKey); } else { // Add externalSources.put(externalSourceKey, externalSourceUrl); } // Encode String externalSourcesEncoded = EncodingUtil.encodeToString((Serializable) externalSources); preferences.setValue("externalSourcesEncoded", externalSourcesEncoded); preferences.store(); response.setRenderParameter("action", "editExternalSources"); } /** * Action method to step one period ahead. * * @param model the model */ @RenderMapping(params = "navigate=next") public String nextWeek(ModelMap model, RenderRequest request, RenderResponse response) { CalendarEventsPeriod displayPeriod = (CalendarEventsPeriod) model.get(displayPeriodKey); if (displayPeriod != null) { model.put(displayPeriodKey, displayPeriod.next()); } return displayCalendarEvents(model, request, response); } /** * Action method to step one period back. * * @param model the model */ @RenderMapping(params = "navigate=previous") public String previousWeek(ModelMap model, RenderRequest request, RenderResponse response) { CalendarEventsPeriod displayPeriod = (CalendarEventsPeriod) model.get(displayPeriodKey); if (displayPeriod != null) { model.put(displayPeriodKey, displayPeriod.previous()); } return displayCalendarEvents(model, request, response); } /** * Exception handler method. * * @param exception the thrown exception * @return a {@link ModelAndView} */ @ExceptionHandler(Exception.class) public ModelAndView handleException(Exception exception) { final int maxNumber = 1000000; int randomNumber = new Random().nextInt(maxNumber); LOGGER.error(randomNumber + ": " + exception.getMessage(), exception); String errorMessage = "Tekniskt fel. Vid kontakt med systemansvarig uppge nummer " + randomNumber + ". " + "Med hjlp av numret kan teknisk personal lokalisera felet."; ModelAndView mav = new ModelAndView("errorMessage", "errorMessage", errorMessage); return mav; } protected Map<String, String> getExternalSources(PortletPreferences preferences) throws IOException, ClassNotFoundException { String externalSourcesEncoded = preferences.getValue("externalSourcesEncoded", null); Map<String, String> externalSources; if (externalSourcesEncoded != null) { // Decode externalSources = decodeExternalSources(externalSourcesEncoded); } else { externalSources = new HashMap<String, String>(); } return externalSources; } protected String getFormattedDateIntervalToTitle(CalendarEventsPeriod displayPeriod, Locale locale) { DateTimeFormatter formatter = DateTimeFormat.forPattern(TIME_FORMAT).withLocale(locale); StringBuilder title = new StringBuilder(TIME_FORMAT.length() * 2 + " - ".length()); title.append(formatter.print(displayPeriod.getStartDate())); title.append(" - "); title.append(formatter.print(displayPeriod.getEndDate())); return title.toString(); } }