ServerInfoController.java

/*
 * Copyright 2023 Global Crop Diversity Trust
 *
 * Licensed 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.gringlobal.brapi.v2.impl;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.gringlobal.brapi.BaseBrAPIController;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import uk.ac.hutton.ics.brapi.resource.base.BaseResult;
import uk.ac.hutton.ics.brapi.resource.base.BrapiCall;
import uk.ac.hutton.ics.brapi.resource.base.BrapiCall.Version;
import uk.ac.hutton.ics.brapi.resource.core.serverinfo.ServerInfo;
import uk.ac.hutton.ics.brapi.server.core.serverinfo.BrapiServerInfoResource;

@RestController("brApi21serverinfo")
@RequestMapping(BaseBrAPIController.BRAPIv2_BASE)
@Slf4j
public class ServerInfoController implements InitializingBean, BrapiServerInfoResource {

	@Value("${base.url}")
	private URL baseUrl;
	@Value("${build.name}")
	private String buildName;
	@Value("${build.version}")
	private String buildVersion;

	@Value("${brapi.contact:support+brapi@ggce.genesys-pgr.org}")
	private String brapiContact;
	@Value("${brapi.location:In the cloud}")
	private String brapiLocation;

	private List<BrapiCall> supportedCalls;


	@Override
	public void afterPropertiesSet() throws Exception {
		// Scan the org.genesys.server.brapi package for @Controller annotations
		supportedCalls = new ArrayList<>();
		ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
		scanner.addIncludeFilter(new AnnotationTypeFilter(Controller.class));
		scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class));

		for (BeanDefinition bd : scanner.findCandidateComponents(ServerInfoController.class.getPackageName())) { // Scan this package
			registerBrAPIMethods(bd);
		}

		supportedCalls = ListUtils.unmodifiableList(supportedCalls);
		supportedCalls.forEach(call -> {
			log.info("BrAPI {} {} {} {}", call.getVersions(), call.getMethods(), call.getService(), call.getContentTypes());
		});
	}

	private void registerBrAPIMethods(BeanDefinition bd) throws ClassNotFoundException {
		Class<?> clazz = Class.forName(bd.getBeanClassName());

		String apiPrefix = "";
		if (clazz.isAnnotationPresent(RequestMapping.class)) {
			RequestMapping mapping = clazz.getAnnotation(RequestMapping.class);
			apiPrefix = mapping.value()[0];
		} else if (clazz.isAnnotationPresent(RestController.class)) {
			RestController mapping = clazz.getAnnotation(RestController.class);
			apiPrefix = mapping.value();
		}

		for (Method m : clazz.getDeclaredMethods()) {
			var call = registerBrAPIMethod(apiPrefix, m);
			if (call != null) {
				log.debug("BrAPI call {}#{}", m.getDeclaringClass().getName(), m.getName());
				call.addVersion(Version.TWO_ONE);
				call.addVersion(Version.TWO_ZERO);
				if (StringUtils.startsWith(call.getService(), BaseBrAPIController.BRAPIv2_BASE)) {
					call.setService(call.getService().substring(BaseBrAPIController.BRAPIv2_BASE.length()));
				}
				if (StringUtils.startsWith(call.getService(), "/")) {
					call.setService(call.getService().substring(1));
				}
				supportedCalls.add(call);
			} else {
				log.debug("BrAPI call not generated for {}#{}", m.getDeclaringClass().getName(), m.getName());
			}
		}
	}

	private BrapiCall registerBrAPIMethod(String apiPrefix, Method m) {
		if (m.isAnnotationPresent(RequestMapping.class)) {
			RequestMapping mapping = m.getAnnotation(RequestMapping.class);
			var call = new BrapiCall(apiPrefix + mapping.value()[0]);
			Arrays.stream(mapping.method()).map(RequestMethod::name).map(BrapiCall.Method::valueOf).forEach(call::addMethod);
//			Arrays.stream(mapping.produces()).map(null).forEach(call::addContentType);
			call.addContentType(BrapiCall.ContentType.json);
			call.addDataType(BrapiCall.DataType.json);
			return call;

		} else if (m.isAnnotationPresent(GetMapping.class)) {
			GetMapping mapping = m.getAnnotation(GetMapping.class);
			var call = new BrapiCall(apiPrefix + mapping.value()[0]).addMethod(BrapiCall.Method.GET);
			//, mapping.produces()));
			call.addContentType(BrapiCall.ContentType.json); // Fixed
			call.addDataType(BrapiCall.DataType.json);
			return call;

		} else if (m.isAnnotationPresent(PostMapping.class)) {
			PostMapping mapping = m.getAnnotation(PostMapping.class);
			var call = new BrapiCall(apiPrefix + mapping.value()[0]).addMethod(BrapiCall.Method.POST);
			//, mapping.produces()));
			call.addContentType(BrapiCall.ContentType.json); // Fixed
			call.addDataType(BrapiCall.DataType.json);
			return call;

		} else if (m.isAnnotationPresent(DeleteMapping.class)) {
			DeleteMapping mapping = m.getAnnotation(DeleteMapping.class);
			var call = new BrapiCall(apiPrefix + mapping.value()[0]).addMethod(BrapiCall.Method.DELETE);
			//, mapping.produces()));
			call.addContentType(BrapiCall.ContentType.json); // Fixed
			call.addDataType(BrapiCall.DataType.json);
			return call;

		} else if (m.isAnnotationPresent(PutMapping.class)) {
			PutMapping mapping = m.getAnnotation(PutMapping.class);
			var call = new BrapiCall(apiPrefix + mapping.value()[0]).addMethod(BrapiCall.Method.PUT);
				//, mapping.produces()));
			call.addContentType(BrapiCall.ContentType.json); // Fixed
			call.addDataType(BrapiCall.DataType.json);
			return call;

		}
		return null;
	}

	@Override
	@GetMapping("/serverinfo")
	public BaseResult<ServerInfo> getServerinfo(String contentType) throws SQLException, IOException {
		var serverInfo = new ServerInfo();
		serverInfo
			.setServerName(baseUrl.getHost())
			.setLocation(brapiLocation)
			.setContactEmail(brapiContact)
			.setServerDescription(StringUtils.joinWith(" ", "GGCE", buildVersion, buildName))
			.setCalls(supportedCalls);
		return new BaseResult<ServerInfo>(serverInfo, 0, 0, 0);
	}

}