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.aspect; 015 016import org.gbif.api.annotation.NullToNotFound; 017import org.gbif.ws.NotFoundException; 018 019import java.net.URI; 020import java.util.Optional; 021 022import org.aspectj.lang.JoinPoint; 023import org.aspectj.lang.annotation.AfterReturning; 024import org.aspectj.lang.annotation.Aspect; 025import org.aspectj.lang.reflect.MethodSignature; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028import org.springframework.stereotype.Component; 029import org.springframework.web.bind.annotation.GetMapping; 030import org.springframework.web.bind.annotation.RequestMapping; 031import org.springframework.web.util.UriComponentsBuilder; 032 033/** 034 * This aspect throws a {@link NotFoundException} for every {@code null} return value of a method. 035 */ 036@Component 037@Aspect 038public class NullToNotFoundAspect { 039 040 private static final Logger LOG = LoggerFactory.getLogger(NullToNotFoundAspect.class); 041 042 @AfterReturning( 043 pointcut = "@annotation(org.gbif.api.annotation.NullToNotFound)", 044 returning = "retVal") 045 public void afterReturningAdvice(JoinPoint jp, Object retVal) { 046 if (retVal == null) { 047 NullToNotFound nullToNotFound = getAnnotation(jp); 048 049 // replace pat variables in URI with values 050 URI uri = getTargetUrl(jp, nullToNotFound); 051 052 throw new NotFoundException("Entity not found", uri); 053 } 054 } 055 056 /** 057 * Gets the NullToNotFound annotation. 058 */ 059 private static NullToNotFound getAnnotation(JoinPoint jp) { 060 return ((MethodSignature) jp.getSignature()).getMethod().getAnnotation(NullToNotFound.class); 061 } 062 063 /** 064 * Builds the URL invoked by the request. 065 */ 066 private static URI getTargetUrl(JoinPoint jp, NullToNotFound nullToNotFound) { 067 if (nullToNotFound.useUrlMapping()) { 068 return UriComponentsBuilder.newInstance() 069 .path(getResourceUrl(jp)) 070 .path(getMethodResourceUrl(jp)) 071 .build(jp.getArgs()); 072 } else { 073 return UriComponentsBuilder.newInstance().path(nullToNotFound.value()).build(jp.getArgs()); 074 } 075 } 076 077 /** 078 * Ensures the url is surrounded by '/'. 079 */ 080 private static String addSurroundingSlashes(String url) { 081 String resultUrl = url.endsWith("/") ? url : url + '/'; 082 return resultUrl.startsWith("/") ? resultUrl : '/' + resultUrl; 083 } 084 085 /** 086 * Gets the Resource URL from the RequestMapping annotation if it exists. 087 */ 088 private static String getResourceUrl(JoinPoint jp) { 089 return Optional.ofNullable(jp.getTarget().getClass().getAnnotation(RequestMapping.class)) 090 .map(rm -> rm.value()[0]) 091 .map(NullToNotFoundAspect::addSurroundingSlashes) 092 .orElse(""); 093 } 094 095 /** 096 * Gets the value of the GetMapping annotation. 097 */ 098 private static String getMethodResourceUrl(JoinPoint jp) { 099 return Optional.ofNullable( 100 ((MethodSignature) jp.getSignature()).getMethod().getAnnotation(GetMapping.class)) 101 .map(gm -> gm.value()[0]) 102 .orElse(""); 103 } 104}