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.hooks;
019
020import io.stallion.exceptions.UsageException;
021import io.stallion.monitoring.HealthTrackingHookHandler;
022import io.stallion.restfulEndpoints.XSRFHooks;
023import io.stallion.services.Log;
024import io.stallion.settings.Settings;
025
026import java.lang.reflect.Modifier;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import static io.stallion.utils.Literals.*;
032
033
034public class HookRegistry {
035    private Map<Class<? extends HookHandler>, List<HookHandler>> hooksByClass = new HashMap<>();
036    private Map<Class<? extends ChainedHook>, List<ChainedHook>> chainsByClass = new HashMap<>();
037    private Map<Class<? extends FirstValueHook>, List<FirstValueHook>> firstValueHooksByClass = new HashMap<>();
038
039    private static HookRegistry _instance;
040
041    public static HookRegistry instance() {
042        if (_instance == null) {
043            throw new UsageException("HookRegistry ");
044        }
045        return _instance;
046    }
047
048    public static HookRegistry load() {
049        _instance = new HookRegistry();
050        registerDefaultHandlers();
051        return _instance;
052    }
053
054    public static void shutdown() {
055        _instance = null;
056    }
057
058    public static void registerDefaultHandlers() {
059        XSRFHooks.register();
060        instance().register(new HealthTrackingHookHandler());
061    }
062
063    public void register(HookHandler handler) {
064        Class base = getAbstractBaseClass(handler.getClass());
065
066        if (base == null) {
067            throw new UsageException("You tried to register a handler that does not inherit from an abstract handler class. There is no way this handler can be triggered.");
068        }
069
070        if (!hooksByClass.containsKey(base)) {
071            hooksByClass.put(base, list());
072        }
073        if (!hooksByClass.get(base).contains(handler)) {
074            hooksByClass.get(base).add(handler);
075        }
076    }
077
078    public void register(ChainedHook handler) {
079        Class base = getAbstractBaseClass(handler.getClass());
080        if (base == null) {
081            throw new UsageException("You tried to register a handler that does not inherit from an abstract handler class. There is no way this handler can be triggered.");
082        }
083        if (!chainsByClass.containsKey(handler.getClass())) {
084            chainsByClass.put(base, list());
085        }
086        if (!chainsByClass.get(base).contains(handler)) {
087            chainsByClass.get(base).add(handler);
088        }
089    }
090
091
092    public void register(FirstValueHook handler) {
093        Class base = getAbstractBaseClass(handler.getClass());
094        if (base == null) {
095            throw new UsageException("You tried to register a handler that does not inherit from an abstract handler class. There is no way this handler can be triggered.");
096        }
097        if (!firstValueHooksByClass.containsKey(handler.getClass())) {
098            firstValueHooksByClass.put(base, list());
099        }
100        if (!firstValueHooksByClass.get(base).contains(handler)) {
101            firstValueHooksByClass.get(base).add(handler);
102        }
103    }
104
105
106    private Class getAbstractBaseClass(Class cls) {
107        Class base = cls.getSuperclass();
108        if (base.isAssignableFrom(HookHandler.class)) {
109            return null;
110        }
111        if (base.isAssignableFrom(ChainedHook.class)) {
112            return null;
113        }
114        if (base.isAssignableFrom(FirstValueHook.class)) {
115            return null;
116        }
117
118        for (int x=0; x< 10;x++) {
119            if (base == null) {
120                return null;
121            }
122            if (Modifier.isAbstract(base.getModifiers())) {
123                return base;
124            }
125            base = cls.getSuperclass();
126        }
127        return null;
128    }
129
130    public <T, V> T find(Class<? extends FirstValueHook<T, V>> cls, V arg) {
131        if (firstValueHooksByClass.containsKey(cls)) {
132            for (FirstValueHook<T, V> hook: firstValueHooksByClass.get(cls)) {
133                T val = hook.find(arg);
134                if (val != null) {
135                    return val;
136                }
137            }
138        }
139        return null;
140    }
141
142    public <T> void dispatch(Class<? extends HookHandler> handlerClass, T arg) {
143        if (!Modifier.isAbstract(handlerClass.getModifiers())) {
144            throw new UsageException("You can only dispatch events for an abstract class. Registered handlers will create implementations of this abstract class and then register them to be run when your event is dispatched.");
145        }
146        if (hooksByClass.containsKey(handlerClass)) {
147            for (HookHandler handler : hooksByClass.get(handlerClass)) {
148                try {
149                    handler.handle(arg);
150                } catch (Exception e) {
151                    if (Settings.instance().isStrict()) {
152                        throw new RuntimeException(e);
153                    } else {
154                        Log.exception(e, "Exception running handler {0} with arg {1}", handler.getClass().getName(), arg);
155                    }
156                }
157            }
158        }
159    }
160
161    public <T> T chain(Class<? extends ChainedHook> chainClass, T arg) {
162        if (!Modifier.isAbstract(chainClass.getModifiers())) {
163            throw new UsageException("You can only dispatch events for an abstract class. Registered handlers will create implementations of this abstract class and then register them to be run when your event is dispatched.");
164        }
165        if (chainsByClass.containsKey(chainClass)) {
166            for (ChainedHook chain : chainsByClass.get(chainClass)) {
167                arg = (T)chain.chain(arg);
168            }
169        }
170        return arg;
171    }
172}