001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *     http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.gbif.ws.server.provider;
015
016import org.gbif.ws.WebApplicationException;
017
018import java.text.MessageFormat;
019import java.text.ParseException;
020import java.text.SimpleDateFormat;
021import java.util.Calendar;
022import java.util.Date;
023import java.util.Optional;
024
025import org.springframework.core.MethodParameter;
026import org.springframework.http.HttpStatus;
027import org.springframework.web.bind.support.WebDataBinderFactory;
028import org.springframework.web.context.request.NativeWebRequest;
029import org.springframework.web.method.support.HandlerMethodArgumentResolver;
030import org.springframework.web.method.support.ModelAndViewContainer;
031
032/**
033 * Provider that accepts and transforms partial Dates. For example: 01-2018 or 01/2018 will be
034 * translated into 01-01-2018.
035 */
036public class PartialDateHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
037
038  private static final String YEAR_ONLY_FORMAT = "yyyy";
039  private static final String[] SUPPORTED_FORMATS =
040      new String[] {"yyyy/MM", "yyyy-MM", YEAR_ONLY_FORMAT};
041
042  @Override
043  public boolean supportsParameter(MethodParameter parameter) {
044    return Date.class.equals(parameter.getParameterType())
045        && parameter.getParameterAnnotation(PartialDate.class) != null;
046  }
047
048  @Override
049  public Object resolveArgument(
050      MethodParameter parameter,
051      ModelAndViewContainer mavContainer,
052      NativeWebRequest webRequest,
053      WebDataBinderFactory binderFactory) {
054    final String paramName = parameter.getParameterName();
055    final String paramValue = paramName != null ? webRequest.getParameter(paramName) : null;
056    return Optional.ofNullable(paramValue)
057        .map(value -> tryDateParse(value, paramName))
058        .orElse(null);
059  }
060
061  /**
062   * Tries to parse the input using the supported formats. Adjust the date to the first or last day
063   * of the month depending on the param name.
064   */
065  private Date tryDateParse(String dateValue, String paramName) {
066    if (dateValue != null && !dateValue.isEmpty()) {
067      for (String dateFormat : SUPPORTED_FORMATS) {
068        try {
069          Date date = new SimpleDateFormat(dateFormat).parse(dateValue);
070          Calendar cal = Calendar.getInstance();
071          cal.setTime(date);
072          if (paramName.startsWith("from")) {
073            cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH));
074          } else if (paramName.startsWith("to")) {
075            if (YEAR_ONLY_FORMAT.equals(dateFormat)) {
076              cal.set(Calendar.MONTH, cal.getActualMaximum(Calendar.MONTH));
077            }
078            cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
079          }
080          return cal.getTime();
081        } catch (ParseException ex) {
082          // DO NOTHING
083        }
084      }
085    }
086    throw new WebApplicationException(
087        MessageFormat.format("Unaccepted parameter value {0} : {1}", paramName, dateValue),
088        HttpStatus.BAD_REQUEST);
089  }
090}