org.jasig.schedassist.portlet.webflow.FlowHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.schedassist.portlet.webflow.FlowHelper.java

Source

/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you 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.
 */

package org.jasig.schedassist.portlet.webflow;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.portlet.WindowState;

import net.fortuna.ical4j.model.component.VEvent;

import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.schedassist.SchedulingException;
import org.jasig.schedassist.model.AvailableBlock;
import org.jasig.schedassist.model.CommonDateOperations;
import org.jasig.schedassist.model.IScheduleOwner;
import org.jasig.schedassist.model.Preferences;
import org.jasig.schedassist.model.Relationship;
import org.jasig.schedassist.model.VisibleSchedule;
import org.jasig.schedassist.model.VisibleScheduleRequestConstraints;
import org.jasig.schedassist.model.VisibleWindow;
import org.jasig.schedassist.portlet.EventCancellation;
import org.jasig.schedassist.portlet.IPortletScheduleVisitor;
import org.jasig.schedassist.portlet.IPortletScheduleVisitorFactory;
import org.jasig.schedassist.portlet.PortletSchedulingAssistantService;
import org.jasig.schedassist.portlet.ScheduleOwnerNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.webflow.context.ExternalContext;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;

/**
 * Helper that wraps {@link PortletSchedulingAssistantService} functions for web flows.
 * 
 * @author Nicholas Blair, nblair@doit.wisc.edu
 * @version $Id: FlowHelper.java $
 */
@Service("flowHelper")
public class FlowHelper {

    protected static final String YES = "yes";
    protected static final String NO = "no";
    private Log LOG = LogFactory.getLog(this.getClass());
    private PortletSchedulingAssistantService schedulingAssistantService;
    private IPortletScheduleVisitorFactory scheduleVisitorFactory;
    private String availableWebBaseUrl;
    private String advisorUrl;
    private String profileSearchUrl;

    public static final String CURRENT_USER_ATTR = FlowHelper.class.getName() + ".CURRENT_USER";

    /**
     * @param schedulingAssistantService the portletAvailableService to set
     */
    @Autowired
    public void setPortletAvailableService(
            @Qualifier("portlet") PortletSchedulingAssistantService schedulingAssistantService) {
        this.schedulingAssistantService = schedulingAssistantService;
    }

    /**
     * @param scheduleVisitorFactory the scheduleVisitorFactory to set
     */
    @Autowired
    public void setScheduleVisitorFactory(IPortletScheduleVisitorFactory scheduleVisitorFactory) {
        this.scheduleVisitorFactory = scheduleVisitorFactory;
    }

    /**
     * @param availableWebBaseUrl the availableWebBaseUrl to set
     */
    @Autowired
    public void setAvailableWebBaseUrl(String availableWebBaseUrl) {
        Validate.notEmpty(availableWebBaseUrl, "availableWebBaseUrl property must not be empty");
        this.availableWebBaseUrl = availableWebBaseUrl;
        if (!this.availableWebBaseUrl.endsWith("/")) {
            this.availableWebBaseUrl += "/";
        }
        this.advisorUrl = this.availableWebBaseUrl + "public/advisors.html";
        this.profileSearchUrl = this.availableWebBaseUrl + "public/index.html";
    }

    /**
     * @return the availableWebBaseUrl
     */
    public String getAvailableWebBaseUrl() {
        return availableWebBaseUrl;
    }

    /**
     * @return the advisorUrl
     */
    public String getAdvisorUrl() {
        return advisorUrl;
    }

    /**
     * @return the profileSearchUrl
     */
    public String getProfileSearchUrl() {
        return profileSearchUrl;
    }

    public void setNormalWindowState(ExternalContext context) {
        MutableAttributeMap map = context.getRequestMap();
        map.put("portletWindowState", WindowState.NORMAL);
    }

    /**
     * 
     * @param dateTimePhrase
     * @return
     * @throws ParseException
     */
    public Date convertDateTime(String dateTimePhrase) throws ParseException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("convertDateTime called on " + dateTimePhrase);
        }
        SimpleDateFormat df = CommonDateOperations.getDateTimeFormat();
        return df.parse(dateTimePhrase);
    }

    /**
     * 
     * @return the current {@link IPortletScheduleVisitor} for the request.
     */
    public IPortletScheduleVisitor getCurrentVisitor() {
        RequestContext requestContext = RequestContextHolder.getRequestContext();
        PortletRequest request = (PortletRequest) requestContext.getExternalContext().getNativeRequest();
        IPortletScheduleVisitor visitor = this.scheduleVisitorFactory.getPortletScheduleVisitor(request);
        PortletSession portletSession = request.getPortletSession();
        portletSession.setAttribute(CURRENT_USER_ATTR, visitor.getAccountId(), PortletSession.APPLICATION_SCOPE);
        return visitor;
    }

    /**
     * 
     * @return
     */
    public boolean isCurrentVisitorEligible() {
        IPortletScheduleVisitor visitor = getCurrentVisitor();
        boolean result = visitor.isEligible();
        if (LOG.isDebugEnabled()) {
            LOG.debug(visitor + " has eligibility: " + result);
        }
        return result;
    }

    /**
     * 
     * @return
     */
    public List<Relationship> getRelationshipsForCurrentVisitor() {
        if (LOG.isDebugEnabled()) {
            LOG.debug("enter getRelationshipsForCurrentVisitor");
        }
        final String visitorUsername = getCurrentVisitor().getAccountId();
        List<Relationship> relationships = this.schedulingAssistantService.relationshipsForVisitor(visitorUsername);
        if (LOG.isDebugEnabled()) {
            LOG.debug("found " + relationships.size() + " relationships");
        }
        return relationships;
    }

    /**
     * 
     * @param owner
     * @return
     */
    public boolean isOwnerSamePersonAsCurrentVisitor(IScheduleOwner owner) {
        return owner.getCalendarAccount().getUsername().equals(getCurrentVisitor().getAccountId());
    }

    /**
     * 
     * @param ownerId
     * @return
     * @throws ScheduleOwnerNotFoundException
     */
    public IScheduleOwner identifyTargetOwner(long ownerId) throws ScheduleOwnerNotFoundException {
        List<Relationship> relationships = getRelationshipsForCurrentVisitor();
        IScheduleOwner target = null;
        for (Relationship r : relationships) {
            if (r.getOwner().getId() == ownerId) {
                target = r.getOwner();
                if (LOG.isInfoEnabled()) {
                    LOG.info(getCurrentVisitor() + " requested schedule of " + target);
                }
                break;
            }
        }

        if (null == target) {
            LOG.error(getCurrentVisitor() + " requested schedule for ownerId= " + ownerId
                    + " and does not have a relationship, throwing ScheduleOwnerNotFoundException");
            throw new ScheduleOwnerNotFoundException(ownerId + " not found");
        }
        return target;
    }

    /**
     * 
     * @param owner
     * @return
     */
    public String getOwnerNoteboard(final IScheduleOwner owner) {
        if (null == owner) {
            return "";
        }
        return owner.getPreference(Preferences.NOTEBOARD);
    }

    /**
     * 
     * @param owner
     * @return the owner's noteboard preference as a {@link List} of sentences
     */
    public List<String> getOwnerNoteboardSentences(final IScheduleOwner owner) {
        if (null == owner) {
            return Collections.emptyList();
        }
        final String ownerNoteboard = owner.getPreference(Preferences.NOTEBOARD);
        String[] sentences = ownerNoteboard.split("\n");
        return Arrays.asList(sentences);
    }

    /**
     * 
     * @param owner
     * @param weekStart
     * @return
     */
    public String testExceededMeetingLimit(IScheduleOwner owner) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("enter testExceededMeetingLimit for " + owner);
        }
        final String visitorUsername = getCurrentVisitor().getAccountId();
        if (owner.hasMeetingLimit()) {
            VisibleSchedule schedule = this.schedulingAssistantService.getVisibleSchedule(visitorUsername,
                    owner.getId());
            if (owner.isExceedingMeetingLimit(schedule.getAttendingCount())) {
                return YES;
            } else {
                return NO;
            }
        } else {
            // owner doesn't limit number of meetings, simply return no
            return NO;
        }
    }

    /**
     * WebFlow can't apparently do type conversion on request parameters.
     * 
     * @param owner
     * @param weekStartParam
     * @return
     * @throws ScheduleOwnerNotFoundException
     */
    public VisibleSchedule getVisibleSchedule(IScheduleOwner owner, String weekStartParam)
            throws ScheduleOwnerNotFoundException {
        return getVisibleSchedule(owner, safeConvertWeekStartParam(weekStartParam));
    }

    /**
     * 
     * @param ownerId
     * @param weekStart
     * @return
     * @throws ScheduleOwnerNotFoundException
     */
    public VisibleSchedule getVisibleSchedule(IScheduleOwner owner, int weekStart)
            throws ScheduleOwnerNotFoundException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("enter getVisibleSchedule, weekstart: " + weekStart + ", owner: " + owner);
        }
        final String visitorUsername = getCurrentVisitor().getAccountId();
        VisibleSchedule schedule = this.schedulingAssistantService.getVisibleSchedule(visitorUsername,
                owner.getId(), weekStart);
        return schedule;
    }

    /**
     * 
     * @param owner
     * @param weekStart
     * @return
     */
    public VisibleScheduleRequestConstraints getVisibleScheduleRequestConstraints(IScheduleOwner owner,
            int weekStart) {
        VisibleScheduleRequestConstraints result = VisibleScheduleRequestConstraints.newInstance(owner, weekStart);
        if (LOG.isDebugEnabled()) {
            LOG.debug(result);
        }
        return result;
    }

    /**
     * 
     * @param owner
     * @param startDateTime
     * @return
     * @throws SchedulingException
     */
    public CreateAppointmentFormBackingObject constructCreateAppointmentFormBackingObject(IScheduleOwner owner,
            Date startDateTime) throws SchedulingException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("enter constructCreateAppointmentFormBackingObject, start: " + startDateTime + ", owner: "
                    + owner);
        }
        // can skip call to validateChosenStartTime as the flow will enforce the check
        AvailableBlock targetBlock = this.schedulingAssistantService.getTargetBlock(owner, startDateTime);
        if (LOG.isDebugEnabled()) {
            LOG.debug("getTargetBlock, startTime= " + startDateTime + " returns " + targetBlock);
        }
        if (targetBlock == null) {
            throw new SchedulingException("requested time is not available");
        }

        CreateAppointmentFormBackingObject fbo = new CreateAppointmentFormBackingObject(targetBlock,
                owner.getPreferredMeetingDurations());
        return fbo;
    }

    /**
     * 
     * @param fbo
     * @param owner
     * @return
     * @throws SchedulingException
     */
    public VEvent createAppointment(CreateAppointmentFormBackingObject fbo, IScheduleOwner owner)
            throws SchedulingException {
        final IPortletScheduleVisitor currentVisitor = getCurrentVisitor();
        final String visitorUsername = currentVisitor.getAccountId();
        AvailableBlock targetBlock = fbo.getTargetBlock();
        if (NO.equals(validateChosenStartTime(owner.getPreferredVisibleWindow(), targetBlock.getStartTime()))) {
            throw new SchedulingException("requested time is no longer within visible window");
        }
        if (fbo.isDoubleLengthAvailable()) {
            LOG.debug("entering doubleLengthAvailable test");
            // check if selected meeting duration matches meeting durations maxLength
            // if it's greater, then we need to look up the next block in the schedule and attempt to combine
            if (fbo.getSelectedDuration() == fbo.getMeetingDurations().getMaxLength()) {
                LOG.debug("selected duration matches double length");
                targetBlock = this.schedulingAssistantService.getTargetDoubleLengthBlock(owner,
                        targetBlock.getStartTime());
                if (targetBlock == null) {
                    throw new SchedulingException("second half of request time is not available");
                }
            }
        }
        if (LOG.isInfoEnabled()) {
            LOG.info(currentVisitor + " submitting scheduleAppointment request with " + owner + " at "
                    + targetBlock);
        }
        VEvent event = this.schedulingAssistantService.scheduleAppointment(visitorUsername, owner.getId(),
                targetBlock, fbo.getReason());
        return event;
    }

    /**
     * Verify the startTime argument is within the window; return {@link #NO} if not.
     * 
     * @param window
     * @param startTime
     * @return {@link #YES} for valid, {@link #NO} for invalid
     */
    public String validateChosenStartTime(VisibleWindow window, Date startTime) {
        final Date currentWindowEnd = window.calculateCurrentWindowEnd();
        if (startTime.before(window.calculateCurrentWindowStart()) || startTime.equals(currentWindowEnd)
                || startTime.after(currentWindowEnd)) {
            LOG.debug("selected startTime (" + startTime + ") is no longer within window " + window.getKey());
            return NO;
        }

        return YES;
    }

    /**
     * 
     * @param owner
     * @param startTime
     * @param endTime
     * @return
     * @throws SchedulingException
     */
    public CancelAppointmentFormBackingObject constructCancelAppointmentFormBackingObject(IScheduleOwner owner,
            Date startTime, Date endTime) throws SchedulingException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("enter constructCancelAppointmentFormBackingObject, start: " + startTime + ", end: " + endTime
                    + " owner: " + owner);
        }
        // try to get the minimum size first
        AvailableBlock targetBlock = this.schedulingAssistantService.getTargetBlock(owner, startTime);
        if (null == targetBlock) {
            throw new SchedulingException("requested time is not available in schedule");
        }

        if (!targetBlock.getEndTime().equals(endTime)) {
            // the returned block doesn't match the specified end time - try grabbing doublelength
            targetBlock = this.schedulingAssistantService.getTargetDoubleLengthBlock(owner, startTime);
            if (null != targetBlock && targetBlock.getEndTime().equals(endTime)) {
                CancelAppointmentFormBackingObject fbo = new CancelAppointmentFormBackingObject(targetBlock);
                return fbo;
            } else {
                throw new SchedulingException("requested time is not available in schedule");
            }

        } else {
            CancelAppointmentFormBackingObject fbo = new CancelAppointmentFormBackingObject(targetBlock);
            return fbo;
        }
    }

    /**
     * 
     * @param fbo
     * @param owner
     * @return the event cancellation details
     * @throws SchedulingException
     */
    public EventCancellation cancelAppointment(CancelAppointmentFormBackingObject fbo, IScheduleOwner owner)
            throws SchedulingException {
        final IPortletScheduleVisitor currentVisitor = getCurrentVisitor();
        final String visitorUsername = currentVisitor.getAccountId();
        if (LOG.isInfoEnabled()) {
            LOG.info(currentVisitor + " submitting cancelAppointment request with " + owner + " at "
                    + fbo.getTargetBlock());
        }
        EventCancellation result = this.schedulingAssistantService.cancelAppointment(visitorUsername, owner.getId(),
                fbo.getTargetBlock(), fbo.getReason());
        return result;
    }

    /**
     * 
     * @param weekStartParam
     * @return
     */
    protected int safeConvertWeekStartParam(String weekStartParam) {
        try {
            int result = Integer.parseInt(weekStartParam);
            if (result < 1) {
                return 0;
            } else {
                return result;
            }
        } catch (NumberFormatException e) {
            return 0;
        }
    }
}