/* * Copyright (C) 2014 Civilian Framework. * * Licensed under the Civilian License (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.civilian-framework.org/license.txt * * 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.civilian.resource.scan; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; import org.civilian.Controller; import org.civilian.controller.ControllerNaming; import org.civilian.internal.controller.MethodAnnotations; import org.civilian.resource.PathParam; import org.civilian.resource.PathParamMap; import org.civilian.response.UriEncoder; import org.civilian.util.Check; import org.civilian.util.ClassUtil; import org.civilian.util.StringUtil; class ResourceFactory { public ResourceFactory(String rootPackage, ControllerNaming naming, PathParamMap pathParams) { rootPackage_ = Check.notNull(rootPackage, "rootPackage"); naming_ = Check.notNull(naming, "naming"); pathParamMap_ = Check.notNull(pathParams, "pathParams"); packages_.put(rootPackage_, new ControllerPackage(root_, rootPackage_)); } public ResourceInfo getRoot() { return root_; } public String getRootPackage() { return rootPackage_; } public ControllerNaming getNaming() { return naming_; } private ControllerPackage getPackage(String name) { if (!name.startsWith(rootPackage_)) throw new IllegalArgumentException("invalid package " + name); ControllerPackage cp = packages_.get(name); if (cp == null) cp = mapPackage(name); return cp; } private ControllerPackage mapPackage(String packageName) { ControllerPackage parent = null; String segment = null; if (!packageName.equals(rootPackage_)) { int p = packageName.lastIndexOf('.'); segment = naming_.packagePart2Segment(packageName.substring(p + 1)); parent = getPackage(packageName.substring(0, p)); } Class<?> infoClass = getPackageInfoClass(packageName); ResourcePart part = map(infoClass, segment, true); ControllerPackage cp = new ControllerPackage(parent, packageName, part); packages_.put(packageName, cp); return cp; } private Class<?> getPackageInfoClass(String packageName) { try { return Class.forName(packageName + ".package-info"); } catch(ClassNotFoundException e) { return null; } } /** * Maps a controller class to a resource. By default the controller resource * extends the package resource by the controller class name. * This method also handles: * - mapping of default controllers, as defined by the DefaultController annotation * - mapping of classes with a PathParam annotation * - mapping of classes with a Path annotation * @param cp the controller package, containing the resource to which the package is mapped * @param c the controller class */ public void mapController(Class<? extends Controller> c) { ControllerPackage cp = getPackage(ClassUtil.getPackageName(c)); String segment = naming_.className2Segment(c.getSimpleName()); ResourcePart part = map(c, segment, false); ResourceInfo ctrlRes = part == null ? cp.resInfo : cp.resInfo.getChild(part); ctrlRes.setControllerInfo(c.getName(), null); for (ResourcePart methodPart : collectMethodParts(c)) { ResourceInfo methodRes = ctrlRes.getChild(methodPart); methodRes.setControllerInfo(c.getName(), methodPart.pathAnnotation); } } private HashSet<ResourcePart> collectMethodParts(Class<? extends Controller> c) { methodParts_.clear(); for (Method method : c.getDeclaredMethods()) { String pathAnno = MethodAnnotations.getPath(method); String path = normPathAnnotation(pathAnno); if (path != null) methodParts_.add(new ResourcePart(encoder_.encode(path), pathAnno)); } return methodParts_; } /** * Reads @PathParam and @Path annotations for package-info and controller classes. * @param segment the default segment derived from the last package part or * the simple name of the controller class. */ private ResourcePart map(Class<?> c, String segment, boolean isPackage) { // check if @PathParam is annotated PathParam<?> pp = null; String pathAnno = null; if (c != null) { org.civilian.annotation.PathParam paramAnnotation = c.getAnnotation(org.civilian.annotation.PathParam.class); if (paramAnnotation != null) { pp = pathParamMap_.get(paramAnnotation.value()); if (pp == null) throw new ScanException(c.getName() + ": annotation @PathParam specifies unknown path parameter '" + paramAnnotation.value() + "'"); } // check if @Path is annotated org.civilian.annotation.Path pathAnnotation = c.getAnnotation(org.civilian.annotation.Path.class); if (pathAnnotation != null) { if (paramAnnotation != null) throw new ScanException(c.getName() + ": cannot specify both @PathParam and @Path annotations"); segment = normPathAnnotation(pathAnnotation.value()); if ((segment == null) && isPackage) throw new ScanException(c.getName() + ": @Path annotation '" + pathAnnotation.value() + "' results in an empy path"); } } if (pp != null) return new ResourcePart(pp); else if (segment != null) return new ResourcePart(encoder_.encode(segment), pathAnno); else return null; } private String normPathAnnotation(String path) { if (path != null) { path = path.trim(); path = StringUtil.cutLeft(path, "/"); path = StringUtil.cutRight(path, "/").trim(); if (path.length() == 0) path = null; } return path; } private ResourceInfo root_ = new ResourceInfo(); private String rootPackage_; private PathParamMap pathParamMap_; private ControllerNaming naming_; private HashMap<String,ControllerPackage> packages_ = new HashMap<>(); private HashSet<ResourcePart> methodParts_ = new HashSet<>(); private UriEncoder encoder_ = new UriEncoder(); }