/* * Copyright 2011 Future Systems, Inc * * 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. */ package org.krakenapps.msgbus.handler; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.felix.ipojo.ConfigurationException; import org.apache.felix.ipojo.InstanceManager; import org.apache.felix.ipojo.PrimitiveHandler; import org.apache.felix.ipojo.metadata.Element; import org.apache.felix.ipojo.parser.FieldMetadata; import org.apache.felix.ipojo.parser.MethodMetadata; import org.apache.felix.ipojo.parser.PojoMetadata; import org.krakenapps.msgbus.MessageBus; import org.krakenapps.msgbus.MessageHandler; import org.krakenapps.msgbus.Request; import org.krakenapps.msgbus.Response; import org.krakenapps.msgbus.Session; import org.krakenapps.msgbus.SessionEventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MsgbusPluginHandler extends PrimitiveHandler implements MessageHandler, SessionEventHandler { final Logger logger = LoggerFactory.getLogger(MsgbusPluginHandler.class.getName()); private MessageBus msgbus; private Map<String, Handler> messageHandlerMap = new ConcurrentHashMap<String, Handler>(); private Set<Handler> sessionHandlers = Collections.newSetFromMap(new ConcurrentHashMap<Handler, Boolean>()); /* * PrimitiveHandler implementations */ @SuppressWarnings("rawtypes") @Override public void configure(Element metadata, Dictionary config) throws ConfigurationException { logger.trace("msgbus handler: configuring component instance [{}]", config.get("instance.name")); injectMessageBus(); registerHandlerMethods(); } @Override public void stateChanged(int state) { if (state == InstanceManager.VALID) { validate(); } else if (state == InstanceManager.INVALID) { invalidate(); } } private void injectMessageBus() { PojoMetadata pojoMetadata = getPojoMetadata(); for (FieldMetadata fieldMetadata : pojoMetadata.getFields()) { if (fieldMetadata.getFieldType().equals("org.krakenapps.msgbus.MessageBus")) { getInstanceManager().register(fieldMetadata, this); } } } private void registerHandlerMethods() { Object o = getInstanceManager().getPojoObject(); if (o == null) { logger.error("msgbus handler: failed to register handler methods (pojo not found)"); return; } Class<?> clazz = o.getClass(); for (Method m : clazz.getDeclaredMethods()) { MsgbusMethod method = m.getAnnotation(MsgbusMethod.class); if (method == null) continue; Set<MsgbusPermission> perms = new HashSet<MsgbusPermission>(); for (Annotation a : m.getAnnotations()) if (a.annotationType() == MsgbusPermission.class) perms.add((MsgbusPermission) a); AllowGuestAccess allowGuestAccess = m.getAnnotation(AllowGuestAccess.class); logger.trace("msgbus handler: msgbus method [{}] detected.", m.getName()); MethodMetadata mm = getPojoMetadata().getMethod(m.getName()); if (mm == null) continue; logger.trace("msgbus handler: annotated method [{}] injected.", m.getName()); Handler handler = new Handler(); handler.plugin = o; handler.method = m; handler.type = method.type(); handler.perms = perms; handler.allowGuestAccess = allowGuestAccess != null; String methodName = clazz.getName() + "." + mm.getMethodName(); logger.trace("msgbus handler: adding [{}] to message handler map.", methodName); if (method.type() == CallbackType.MessageReceived) { messageHandlerMap.put(methodName, handler); logger.trace("msgbus handler: MessageReceived callback [{}] detected.", mm.getMethodName()); } else { sessionHandlers.add(handler); logger.trace("msgbus handler: Session callback [{}] detected.", mm.getMethodName()); } } } @Override public void start() { } @Override public void stop() { } /* * MessageHandler implementations */ @Override public void handleMessage(Request req, Response resp) throws Exception { Session session = req.getSession(); if (session == null) throw new IllegalStateException("session not found for request [" + req.getMethod() + "]"); Handler handler = messageHandlerMap.get(req.getMethod()); if (!handler.allowGuestAccess && isGuest(session)) throw new SecurityException("guest cannot request [" + req.getMethod() + "]"); for (MsgbusPermission perm : handler.perms) { if (!msgbus.checkPermission(session, perm.group(), perm.code())) { String subject = req.getAdminLoginName() == null ? "guest" : "[" + req.getAdminLoginName() + "]"; throw new SecurityException(subject + " has no [" + perm.group() + "/" + perm.code() + "] permission"); } } logger.trace("msgbus hanlder: dispatching message [{}] to handler [{}]", req.getMethod(), handler); handler.method.invoke(handler.plugin, new Object[] { req, resp }); } private boolean isGuest(Session session) { if (session.getOrgDomain() != null && session.getAdminLoginName() != null) return false; return true; } @Override public String getClassName() { return getInstanceManager().getPojoObject().getClass().getName(); } @Override public Collection<String> getMethodNames() { Set<String> messageTypes = new HashSet<String>(); for (String messageType : messageHandlerMap.keySet()) { messageTypes.add(messageType); } return messageTypes; } /* * SessionEventHandler implementations */ @Override public void sessionOpened(Session session) { for (Handler handler : sessionHandlers) { if (handler.type == CallbackType.SessionOpened) fireSessionEvent(handler.plugin, handler.method, session); } } @Override public void sessionClosed(Session session) { for (Handler handler : sessionHandlers) { if (handler.type == CallbackType.SessionClosed) fireSessionEvent(handler.plugin, handler.method, session); } } private void fireSessionEvent(Object plugin, Method method, Session session) { try { method.invoke(plugin, new Object[] { session }); } catch (Exception e) { logger.warn("kraken msgbus: session event handler should not throw any exception", e); } } /* * Start and stop */ private void validate() { if (msgbus == null) { logger.warn("msgbus handler: null msgbus, restart bundle required."); return; } msgbus.register((MessageHandler) this); if (sessionHandlers.size() != 0) msgbus.register((SessionEventHandler) this); } private void invalidate() { if (msgbus == null) return; msgbus.unregister((MessageHandler) this); if (sessionHandlers.size() != 0) msgbus.unregister((SessionEventHandler) this); } class Handler { public Object plugin; public Method method; public CallbackType type; public Set<MsgbusPermission> perms = null; public boolean allowGuestAccess = false; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + getOuterType().hashCode(); result = prime * result + (allowGuestAccess ? 1231 : 1237); result = prime * result + ((method == null) ? 0 : method.hashCode()); result = prime * result + ((perms == null) ? 0 : perms.hashCode()); result = prime * result + ((plugin == null) ? 0 : plugin.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Handler other = (Handler) obj; if (!getOuterType().equals(other.getOuterType())) return false; if (allowGuestAccess != other.allowGuestAccess) return false; if (method == null) { if (other.method != null) return false; } else if (!method.equals(other.method)) return false; if (perms == null) { if (other.perms != null) return false; } else if (!perms.equals(other.perms)) return false; if (plugin == null) { if (other.plugin != null) return false; } else if (!plugin.equals(other.plugin)) return false; if (type != other.type) return false; return true; } @Override public String toString() { return plugin + ":" + method; } private MsgbusPluginHandler getOuterType() { return MsgbusPluginHandler.this; } } }