package no.niths.application.rest.misc; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import no.niths.application.rest.RESTConstants; import no.niths.application.rest.lists.RestResourceList; import no.niths.application.rest.misc.interfaces.DiscoverabilityController; import no.niths.common.constants.DomainConstantNames; import org.slf4j.Logger; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.util.ClassUtils; import org.springframework.util.SystemPropertyUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; /** * * Discoverability Controller * <p> * Requests against the root uri returns a list of all valid domains in the header * </p> * <p> * Requests against root uri + api returns a list of all resources with * description in JSON or XML format (specified in request header). * For html, uri is api/html * </p> * */ @Controller public class DiscoverabilityControllerImpl implements DiscoverabilityController { Logger logger = org.slf4j.LoggerFactory .getLogger(DiscoverabilityControllerImpl.class); private RestResourceList list = new RestResourceList(); private final static String HTML_PATH = "api/html"; private final static String JSON_XML_PATH = "api"; /** * * Returns a list of all services in the API. * <p> * Contains path, method type, path variables, status messages, status code, * headers and authorization * </p> * * @param req * the http header, specifies return format * @return a list of resources in the API */ @Override @RequestMapping(value = JSON_XML_PATH, method = RequestMethod.GET, headers = RESTConstants.ACCEPT_HEADER) @ResponseBody public List<RestResource> getApi(HttpServletRequest req) { String url = getBaseUrl(req.getRequestURL().toString()); generateApiList(url.replace(JSON_XML_PATH, "")); return list; } /** * Returns a HTML view of the API * * @param req * the http header * @return html view of services in the API */ @Override @RequestMapping(value = HTML_PATH, method = RequestMethod.GET) public ModelAndView getApiHtml(HttpServletRequest req) { ModelAndView view = new ModelAndView("api"); String url = getBaseUrl(req.getRequestURL().toString()); generateApiList(url.replace(HTML_PATH, "")); view.addObject("api", list); return view; } private String getBaseUrl(String reqUrl){ String url = reqUrl; if(reqUrl.endsWith("/")){ url = url.substring(0, url.length() - 1); } return url; } // Reads all classes in rest package and // generates a list of resources @SuppressWarnings({ "unchecked", "rawtypes" }) private void generateApiList(String baseUrl) { list.clear(); // Get all classes in given package with @controller // Read class annotations and then read all method annotations try { List<Class> all = findMyTypes("no.niths.application.rest"); for (Class c : all) { boolean isFirst = false; RestResource resource = null; if (c.getAnnotation(RequestMapping.class) != null || c.getSimpleName().equals( "AbstractRESTControllerImpl")) { if (c.getSimpleName().equals("AbstractRESTControllerImpl")) { isFirst = true; String url = baseUrl + "DOMAIN/"; resource = new RestResource(url); Method[] ms = c.getMethods(); for (int i = 0; i < ms.length; i++) { if (ms[i].getAnnotation(RequestMapping.class) != null) { resource.getMethods().add( handleMethod(ms[i], url)); } } } else { RequestMapping mapClass = (RequestMapping) c .getAnnotation(RequestMapping.class); String url = baseUrl; url += mapClass.value()[0] + "/"; resource = new RestResource(url); Method[] ms = c.getMethods(); for (int i = 0; i < ms.length; i++) { if (ms[i].getAnnotation(RequestMapping.class) != null || ms[i].getAnnotation(PreAuthorize.class) != null) { resource.getMethods().add( handleMethod(ms[i], url)); } } } if (isFirst) { isFirst = false; list.add(0, resource); } else { list.add(resource); } } } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } list.setData(list); } private MethodInfo handleMethod(Method m, String url) { String valuesS = ""; String methodS = ""; String headersS = ""; String reasonS = ""; String responseCodeS = ""; String resAuth = " "; RequestMapping map = (RequestMapping) m .getAnnotation(RequestMapping.class); if (map != null) { String[] head = map.headers(); for (String h : head) { headersS += h + " "; } String[] values = map.value(); for (String v : values) { valuesS += url + v + " "; } RequestMethod[] methods = map.method(); for (RequestMethod rm : methods) { methodS += rm.name() + " "; } } //Add the overriden methods from AbstractRestController (dirty) if (valuesS.equals("")){ valuesS = url; if(m.getName().equals("create")){ methodS = "POST"; reasonS = "Created"; responseCodeS = "201"; headersS = "Content-type: application/json,application/xml"; }else if(m.getName().equals("getById")){ methodS = "GET"; valuesS += "{id}"; }else if(m.getName().equals("delete")){ methodS = "DELETE"; valuesS += "{id}"; reasonS = "Deleted"; responseCodeS = "200"; }else if(m.getName().equals("update")){ methodS = "PUT"; reasonS = "Update OK"; responseCodeS = "200"; headersS = "Content-type: application/json,application/xml"; }else if(m.getName().equals("getAll")){ if(m.getParameterTypes().length > 1 ){ //paginated url += "paginated/{firstResult}/{maxResults}"; } methodS = "GET"; headersS = "Accept: application/json,application/xml"; } } ResponseStatus status = m.getAnnotation(ResponseStatus.class); if (status != null) { reasonS = status.reason(); responseCodeS = status.value().toString(); } PreAuthorize pre = m.getAnnotation(PreAuthorize.class); if (pre != null) { // Extract the role names from EL resAuth = handleAuthAnnotation(pre.value()); } if(responseCodeS.equals("")){ responseCodeS = "200"; } return new MethodInfo(valuesS.trim(), methodS.trim(), headersS.trim().replace('=', ':'), reasonS.trim(), responseCodeS.trim(), resAuth.trim()); } private String handleAuthAnnotation(String val) { Pattern pattern = Pattern.compile("ROLE_([\\w]*)"); Matcher matcher = pattern.matcher(val); String text = ""; while (matcher.find()) { text += matcher.group() + " "; } return text; } @Override @RequestMapping(value = "/", method = RequestMethod.GET) @ResponseBody public String root(HttpServletRequest req, HttpServletResponse res) { res.setContentType(MediaType.APPLICATION_JSON_VALUE); return buildURLs(req.getRequestURL().toString()); } @SuppressWarnings("rawtypes") private List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException { ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory( resourcePatternResolver); List<Class> candidates = new ArrayList<Class>(); String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + "/" + "**/*.class"; Resource[] resources = resourcePatternResolver .getResources(packageSearchPath); for (Resource resource : resources) { if (resource.isReadable()) { MetadataReader metadataReader = metadataReaderFactory .getMetadataReader(resource); if (isCandidate(metadataReader)) { candidates.add(Class.forName(metadataReader .getClassMetadata().getClassName())); } } } return candidates; } private String resolveBasePackage(String basePackage) { return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils .resolvePlaceholders(basePackage)); } @SuppressWarnings({ "rawtypes", "unchecked" }) private boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException { try { Class c = Class.forName(metadataReader.getClassMetadata() .getClassName()); if ((c.getAnnotation(Controller.class) != null || c.getSimpleName().equals("AbstractRESTControllerImpl")) && !(c.getSimpleName().equals("ExampleControllerImpl"))) { return true; } } catch (Throwable e) { } return false; } private String buildURLs(String rootURL) { StringBuilder jsonBuilder = new StringBuilder("["); try { for (Field field : DomainConstantNames.class.getFields()) { jsonBuilder.append(String.format("{ \"url\": \"%s%s\"},", rootURL, field.get(null))); } } catch (IllegalArgumentException | IllegalAccessException e) { e.printStackTrace(); } return jsonBuilder.deleteCharAt(jsonBuilder.length() - 1).append(']') .toString(); } }