001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.restfulEndpoints;
019
020import com.fasterxml.jackson.annotation.JsonView;
021import io.stallion.exceptions.ConfigException;
022import io.stallion.exceptions.UsageException;
023import io.stallion.services.Log;
024import io.stallion.settings.Settings;
025import io.stallion.users.Role;
026import org.apache.commons.lang3.StringUtils;
027
028import javax.ws.rs.*;
029import java.lang.annotation.Annotation;
030import java.lang.reflect.Method;
031import java.lang.reflect.Parameter;
032import java.util.ArrayList;
033import java.util.List;
034import java.util.regex.Pattern;
035
036import static io.stallion.utils.Literals.empty;
037
038
039public class ResourceToEndpoints {
040
041    private String basePath = "";
042
043    public ResourceToEndpoints() {
044
045    }
046
047    public ResourceToEndpoints(String basePath) {
048        this.basePath = basePath;
049    }
050
051    public List<JavaRestEndpoint> convert(EndpointResource resource) {
052        Class cls = resource.getClass();
053        List<JavaRestEndpoint> endpoints = new ArrayList<>();
054
055        // Get defaults from the resource
056
057        Role defaultMinRole = Settings.instance().getUsers().getDefaultEndpointRoleObj();
058        MinRole minRoleAnno = (MinRole)cls.getAnnotation(MinRole.class);
059        if (minRoleAnno != null) {
060            defaultMinRole = minRoleAnno.value();
061        }
062
063        String defaultProduces = "text/html";
064        Produces producesAnno = (Produces)cls.getAnnotation(Produces.class);
065        if (producesAnno != null && producesAnno.value().length > 0) {
066            defaultProduces = producesAnno.value()[0];
067        }
068
069        Path pathAnno = (Path)cls.getAnnotation(Path.class);
070        if (pathAnno != null) {
071            basePath += pathAnno.value();
072        }
073
074        Class defaultJsonViewClass = null;
075        DefaultJsonView jsonView = (DefaultJsonView)cls.getAnnotation(DefaultJsonView.class);
076        if (jsonView != null) {
077            defaultJsonViewClass = jsonView.value();
078        }
079
080
081        for(Method method: cls.getDeclaredMethods()) {
082            JavaRestEndpoint endpoint = new JavaRestEndpoint();
083            endpoint.setRole(defaultMinRole);
084            endpoint.setProduces(defaultProduces);
085            if (defaultJsonViewClass != null) {
086                endpoint.setJsonViewClass(defaultJsonViewClass);
087            }
088
089            Log.finer("Resource class method: {0}", method.getName());
090            for(Annotation anno: method.getDeclaredAnnotations()) {
091                if (Path.class.isInstance(anno)) {
092                    Path pth = (Path)anno;
093                    endpoint.setRoute(getBasePath() + pth.value());
094                } else if (GET.class.isInstance(anno)) {
095                    endpoint.setMethod("GET");
096                } else if (POST.class.isInstance(anno)) {
097                    endpoint.setMethod("POST");
098                } else if (DELETE.class.isInstance(anno)) {
099                    endpoint.setMethod("DELETE");
100                } else if (PUT.class.isInstance(anno)) {
101                    endpoint.setMethod("PUT");
102                } else if (Produces.class.isInstance(anno)) {
103                    endpoint.setProduces(((Produces) anno).value()[0]);
104                } else if (MinRole.class.isInstance(anno)) {
105                    endpoint.setRole(((MinRole) anno).value());
106                } else if (XSRF.class.isInstance(anno)) {
107                    endpoint.setCheckXSRF(((XSRF)anno).value());
108                } else if (JsonView.class.isInstance(anno)) {
109                    Class[] classes = ((JsonView)anno).value();
110                    if (classes == null || classes.length != 1) {
111                        throw new UsageException("JsonView annotation for method " + method.getName() + " must have exactly one view class");
112                    }
113                    endpoint.setJsonViewClass(classes[0]);
114                }
115            }
116            if (!empty(endpoint.getMethod()) && !empty(endpoint.getRoute())) {
117                endpoint.setJavaMethod(method);
118                endpoint.setResource(resource);
119                endpoints.add(endpoint);
120                Log.fine("Register endpoint {0} {1}", endpoint.getMethod(), endpoint.getRoute());
121            } else {
122                continue;
123            }
124            int x = -1;
125            for(Parameter param: method.getParameters()) {
126                x++;
127                RequestArg arg = new RequestArg();
128                for (Annotation anno: param.getAnnotations()) {
129                    Log.finer("Param Annotation is: {0}, {1}", anno, anno.getClass().getName());
130                    if (BodyParam.class.isInstance(anno)) {
131                        BodyParam bodyAnno = (BodyParam)(anno);
132                        arg.setType("BodyParam");
133                        arg.setName(((BodyParam) anno).value());
134                        arg.setAnnotationClass(BodyParam.class);
135                        arg.setRequired(bodyAnno.required());
136                        arg.setEmailParam(bodyAnno.isEmail());
137                        arg.setMinLength(bodyAnno.minLength());
138                        arg.setAllowEmpty(bodyAnno.allowEmpty());
139                        if (!empty(bodyAnno.validationPattern())) {
140                            arg.setValidationPattern(Pattern.compile(bodyAnno.validationPattern()));
141                        }
142                    } else if (ObjectParam.class.isInstance(anno)) {
143                        ObjectParam oParam = (ObjectParam)anno;
144                        arg.setType("ObjectParam");
145                        arg.setName("noop");
146                        if (oParam.targetClass() == null || oParam.targetClass().equals(Object.class)) {
147                            arg.setTargetClass(param.getType());
148                        } else {
149                            arg.setTargetClass(oParam.targetClass());
150                        }
151                        arg.setAnnotationClass(ObjectParam.class);
152                    } else if (MapParam.class.isInstance(anno)) {
153                        arg.setType("MapParam");
154                        arg.setName("noop");
155                        arg.setAnnotationClass(MapParam.class);
156                    } else if (QueryParam.class.isInstance(anno)) {
157                        arg.setType("QueryParam");
158                        arg.setName(((QueryParam)anno).value());
159                        arg.setAnnotationClass(QueryParam.class);
160                    } else if (PathParam.class.isInstance(anno)) {
161                        arg.setType("PathParam");
162                        arg.setName(((PathParam)anno).value());
163                        arg.setAnnotationClass(PathParam.class);
164                    } else if (DefaultValue.class.isInstance(anno)) {
165                        arg.setDefaultValue(((DefaultValue) anno).value());
166                    }
167                }
168                if (StringUtils.isEmpty(arg.getType()) || StringUtils.isEmpty(arg.getName())) {
169                    throw new ConfigException("The endpoint " + endpoint.getMethod() + " " + endpoint.getRoute() + " (java method is " + endpoint.getJavaMethod().getName() + ")" +
170                            " has an invalid argument. Argument " + x + " does not have a proper Param annotation and/or name.");
171                }
172                endpoint.getArgs().add(arg);
173            }
174        }
175        return endpoints;
176    }
177
178    public String getBasePath() {
179        return basePath;
180    }
181
182    public ResourceToEndpoints setBasePath(String basePath) {
183        this.basePath = basePath;
184        return this;
185    }
186}