package act.controller.meta;
/*-
* #%L
* ACT Framework
* %%
* Copyright (C) 2014 - 2017 ActFramework
* %%
* 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.
* #L%
*/
import act.Act;
import act.app.App;
import act.app.AppClassLoader;
import act.asm.Type;
import act.handler.builtin.controller.ControllerAction;
import act.handler.builtin.controller.Handler;
import act.util.ClassInfoRepository;
import act.util.ClassNode;
import act.util.DestroyableBase;
import org.osgl.http.H;
import org.osgl.mvc.annotation.*;
import org.osgl.util.C;
import org.osgl.util.S;
import javax.enterprise.context.ApplicationScoped;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static act.Destroyable.Util.destroyAll;
/**
* Stores all class level information to support generating of
* {@link ControllerAction request dispatcher}
* and {@link Handler interceptors}
*/
@ApplicationScoped
public final class ControllerClassMetaInfo extends DestroyableBase {
private Type type;
private Type superType;
private boolean isAbstract = false;
private String ctxField = null;
private boolean ctxFieldIsPrivate = true;
private C.Set<String> withList = C.newSet();
private C.List<ActionMethodMetaInfo> actions = C.newList();
// actionLookup index action method by method name
private C.Map<String, ActionMethodMetaInfo> actionLookup = null;
// handlerLookup index handler method by method name
// handler could by action or any kind of interceptors
private C.Map<String, HandlerMethodMetaInfo> handlerLookup = null;
GroupInterceptorMetaInfo interceptors = new GroupInterceptorMetaInfo();
private ControllerClassMetaInfo parent;
private boolean isController;
private boolean possibleController;
private String contextPath;
public ControllerClassMetaInfo className(String name) {
this.type = Type.getObjectType(name);
return this;
}
@Override
protected void releaseResources() {
withList.clear();
destroyAll(actions, ApplicationScoped.class);
actions.clear();
if (null != actionLookup) {
destroyAll(actionLookup.values(), ApplicationScoped.class);
actionLookup.clear();
}
if (null != handlerLookup) {
destroyAll(handlerLookup.values(), ApplicationScoped.class);
handlerLookup.clear();
}
interceptors.destroy();
if (null != parent) parent.destroy();
super.releaseResources();
}
public String className() {
return type.getClassName();
}
public String internalName() {
return type.getInternalName();
}
public Type type() {
return type;
}
public ControllerClassMetaInfo superType(Type type) {
superType = type;
return this;
}
public Type superType() {
return superType;
}
public List<String> withList() {
return C.list(withList);
}
public ControllerClassMetaInfo setAbstract() {
isAbstract = true;
return this;
}
public boolean isAbstract() {
return isAbstract;
}
public boolean isController() {
return isController;
}
public ControllerClassMetaInfo isController(boolean b) {
isController = b;
return this;
}
public boolean possibleController() {
return possibleController;
}
public ControllerClassMetaInfo possibleController(boolean b) {
possibleController = b;
return this;
}
boolean isMyAncestor(ControllerClassMetaInfo clsInfo) {
ControllerClassMetaInfo parentInfo = parent(true);
if (null == parentInfo) {
return false;
}
if (parentInfo.equals(clsInfo)) {
return true;
}
return parentInfo.isMyAncestor(clsInfo);
}
public ControllerClassMetaInfo parent(ControllerClassMetaInfo parentInfo) {
parent = parentInfo;
return this;
}
public ControllerClassMetaInfo parent() {
return parent;
}
public ControllerClassMetaInfo parent(boolean checkClassInfoRepo) {
if (null != parent) {
return parent;
}
if (!checkClassInfoRepo) {
return null;
}
AppClassLoader classLoader = Act.app().classLoader();
ClassInfoRepository repo = classLoader.classInfoRepository();
ClassNode parentNode = repo.node(superType.getClassName());
while(null != parentNode) {
parentNode = parentNode.parent();
if (null != parentNode) {
ControllerClassMetaInfo parentInfo = classLoader.controllerClassMetaInfo(parentNode.name());
if (null != parentInfo) {
return parentInfo;
}
} else {
return null;
}
}
return null;
}
public ControllerClassMetaInfo ctxField(String fieldName, boolean isPrivate) {
ctxField = fieldName;
ctxFieldIsPrivate = isPrivate;
return this;
}
public String nonPrivateCtxField() {
if (null != ctxField) {
return ctxFieldIsPrivate ? null : ctxField;
}
return null == parent ? null : parent.nonPrivateCtxField();
}
public String ctxField() {
if (null != ctxField) {
return ctxField;
}
if (null != parent) {
return parent.nonPrivateCtxField();
}
return null;
}
public boolean hasCtxField() {
return null != ctxField;
}
public boolean ctxFieldIsPrivate() {
return ctxFieldIsPrivate;
}
public ControllerClassMetaInfo addWith(String... classes) {
int len = classes.length;
if (len > 0) {
for (int i = 0; i < len; ++i) {
_addWith(classes[i]);
}
}
return this;
}
public ControllerClassMetaInfo addBefore(InterceptorMethodMetaInfo before) {
interceptors.addBefore(before);
return this;
}
public ControllerClassMetaInfo addAfter(InterceptorMethodMetaInfo after) {
interceptors.addAfter(after);
return this;
}
public ControllerClassMetaInfo addCatch(CatchMethodMetaInfo cat) {
interceptors.addCatch(cat);
return this;
}
public ControllerClassMetaInfo addFinally(InterceptorMethodMetaInfo after) {
interceptors.addFinally(after);
return this;
}
public ControllerClassMetaInfo addInterceptor(InterceptorMethodMetaInfo info, Class<? extends Annotation> type) {
interceptors.add(info, type);
return this;
}
public ControllerClassMetaInfo addAction(ActionMethodMetaInfo info) {
actions.add(info);
return this;
}
public ActionMethodMetaInfo action(String name) {
if (null == actionLookup) {
for (ActionMethodMetaInfo act : actions) {
if (S.eq(name, act.name())) {
return act;
}
}
return null;
}
return actionLookup.get(name);
}
public HandlerMethodMetaInfo handler(String name) {
HandlerMethodMetaInfo info;
if (null == handlerLookup) {
info = action(name);
if (null != info) {
return info;
}
return interceptors.find(name, className());
}
return handlerLookup.get(name);
}
<T extends InterceptorMethodMetaInfo> List<T> convertDerived(List<T> interceptors) {
C.List<T> list = C.newSizedList(interceptors.size());
for (InterceptorMethodMetaInfo derived : interceptors) {
list.add((T)derived.extended(this));
}
return list;
}
public GroupInterceptorMetaInfo interceptors() {
return interceptors;
}
public List<InterceptorMethodMetaInfo> beforeInterceptors() {
return interceptors.beforeList();
}
public List<InterceptorMethodMetaInfo> afterInterceptors() {
return interceptors.afterList();
}
public List<CatchMethodMetaInfo> exceptionInterceptors() {
return interceptors.catchList();
}
public List<InterceptorMethodMetaInfo> finallyInterceptors() {
return interceptors.finallyList();
}
public ControllerClassMetaInfo merge(ControllerClassMetaInfoManager infoBase, App app) {
mergeFromWithList(infoBase, app);
mergeIntoActionList(infoBase, app);
buildActionLookup();
buildHandlerLookup();
return this;
}
public String contextPath() {
if (null != parent) {
if (S.notBlank(contextPath) && contextPath.length() > 1 && contextPath.startsWith("/")) {
return contextPath;
}
String parentContextPath = parent.contextPath();
if (null == contextPath) {
return parentContextPath;
}
if (null == parentContextPath) {
return contextPath;
}
S.Buffer sb = S.newBuffer(parentContextPath);
if (parentContextPath.endsWith("/")) {
sb.deleteCharAt(sb.length() - 1);
}
if (!contextPath.startsWith("/")) {
sb.append("/");
}
sb.append(contextPath);
return sb.toString();
}
return contextPath;
}
public ControllerClassMetaInfo contextPath(String path) {
if (S.blank(path)) {
contextPath = "/";
} else {
contextPath = path;
}
return this;
}
private void _addWith(String clsName) {
withList.add(Type.getType(clsName).getClassName());
}
private void getAllWithList(final Set<String> withList, final ControllerClassMetaInfoManager infoBase) {
withList.addAll(this.withList);
if (null != superType) {
final String superClass = superType.getClassName();
App app = App.instance();
ClassInfoRepository repo = app.classLoader().classInfoRepository();
ControllerClassMetaInfo info = infoBase.controllerMetaInfo(superClass);
String curSuperClass = superClass;
while (null == info && !"java.lang.Object".equals(curSuperClass)) {
ClassNode node = repo.node(curSuperClass);
if (null != node) {
node = node.parent();
}
if (null == node) {
break;
}
curSuperClass = node.name();
info = infoBase.controllerMetaInfo(curSuperClass);
}
if (null != info) {
withList.add(superClass);
}
}
}
private void mergeFromWithList(final ControllerClassMetaInfoManager infoBase, final App app) {
C.Set<String> withClasses = C.newSet();
getAllWithList(withClasses, infoBase);
final ControllerClassMetaInfo me = this;
ClassInfoRepository repo = app.classLoader().classInfoRepository();
for (final String withClass : withClasses) {
String curWithClass = withClass;
ControllerClassMetaInfo withClassInfo = infoBase.controllerMetaInfo(curWithClass);
while (null == withClassInfo && !"java.lang.Object".equals(curWithClass)) {
ClassNode node = repo.node(curWithClass);
if (null != node) {
node = node.parent();
}
if (null == node) {
break;
}
curWithClass = node.name();
withClassInfo = infoBase.controllerMetaInfo(curWithClass);
}
if (null != withClassInfo) {
withClassInfo.merge(infoBase, app);
if (isMyAncestor(withClassInfo)) {
interceptors.mergeFrom(withClassInfo.interceptors, me);
} else {
interceptors.mergeFrom(withClassInfo.interceptors);
}
}
}
}
private void mergeIntoActionList(ControllerClassMetaInfoManager infoBase, App app) {
for (ActionMethodMetaInfo info : actions) {
info.merge(infoBase, app);
info.mergeFromClassInterceptors(interceptors);
}
}
private void buildActionLookup() {
C.Map<String, ActionMethodMetaInfo> lookup = C.newMap();
for (ActionMethodMetaInfo act : actions) {
lookup.put(act.name(), act);
}
actionLookup = lookup;
}
private void buildHandlerLookup() {
C.Map<String, HandlerMethodMetaInfo> lookup = C.newMap();
lookup.putAll(actionLookup);
for (InterceptorMethodMetaInfo info : beforeInterceptors()) {
lookup.put(info.name(), info);
}
for (InterceptorMethodMetaInfo info : afterInterceptors()) {
lookup.put(info.name(), info);
}
for (InterceptorMethodMetaInfo info : exceptionInterceptors()) {
lookup.put(info.name(), info);
}
for (InterceptorMethodMetaInfo info : finallyInterceptors()) {
lookup.put(info.name(), info);
}
handlerLookup = lookup;
}
private static final C.Set<Class<? extends Annotation>> INTERCEPTOR_ANNOTATION_TYPES = C.set(
Before.class, After.class, Catch.class, Finally.class);
public static final C.Set<H.Method> ACTION_METHODS = C.set(H.Method.GET, H.Method.POST, H.Method.PUT, H.Method.DELETE);
private static final Map<Class<? extends Action>, H.Method> METHOD_LOOKUP = C.newMap(
GetAction.class, H.Method.GET,
PostAction.class, H.Method.POST,
PutAction.class, H.Method.PUT,
DeleteAction.class, H.Method.DELETE,
PatchAction.class, H.Method.PATCH
);
public static boolean isActionAnnotation(Class<? extends Annotation> type) {
return METHOD_LOOKUP.containsKey(type) || Action.class == type;
}
public static H.Method lookupHttpMethod(Class annotationClass) {
return METHOD_LOOKUP.get(annotationClass);
}
public static boolean isActionUtilAnnotation(Class<? extends Annotation> type) {
return ActionUtil.class == type;
}
public static boolean isInterceptorAnnotation(Class<? extends Annotation> type) {
return INTERCEPTOR_ANNOTATION_TYPES.contains(type);
}
}