/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.sling.testing.mock.osgi; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.sling.commons.osgi.Order; import org.apache.sling.commons.osgi.ServiceUtil; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; import org.osgi.service.event.EventConstants; import org.osgi.service.event.EventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Mock implementation of {@link EventAdmin}. * From {@link EventConstants} currently only {@link EventConstants#EVENT_TOPIC} is supported. */ @Component(immediate = true, service = EventAdmin.class) public final class MockEventAdmin implements EventAdmin { @Reference(name="eventHandler", service=EventHandler.class, cardinality=ReferenceCardinality.MULTIPLE, policy=ReferencePolicy.DYNAMIC, bind="bindEventHandler", unbind="unbindEventHandler") private final Map<Object, EventHandlerItem> eventHandlers = new TreeMap<Object, EventHandlerItem>(); private ExecutorService asyncHandler; private static final Logger log = LoggerFactory.getLogger(MockEventAdmin.class); @Activate protected void activate(ComponentContext componentContext) { asyncHandler = Executors.newCachedThreadPool(); } @Deactivate protected void deactivate(ComponentContext componentContext) { asyncHandler.shutdownNow(); } @Override public void postEvent(final Event event) { try { asyncHandler.execute(new Runnable() { @Override public void run() { distributeEvent(event); } }); } catch (RejectedExecutionException ex) { // ignore log.debug("Ignore rejected execution: " + ex.getMessage(), ex);; } } @Override public void sendEvent(final Event event) { distributeEvent(event); } private void distributeEvent(Event event) { synchronized (eventHandlers) { for (EventHandlerItem item : eventHandlers.values()) { if (item.matches(event)) { try { item.getEventHandler().handleEvent(event); } catch (Throwable ex) { log.error("Error handlihng event {} in {}", event, item.getEventHandler()); } } } } } protected void bindEventHandler(EventHandler eventHandler, Map<String, Object> props) { synchronized (eventHandlers) { eventHandlers.put(ServiceUtil.getComparableForServiceRanking(props, Order.DESCENDING), new EventHandlerItem(eventHandler, props)); } } protected void unbindEventHandler(EventHandler eventHandler, Map<String, Object> props) { synchronized (eventHandlers) { eventHandlers.remove(ServiceUtil.getComparableForServiceRanking(props, Order.DESCENDING)); } } private static class EventHandlerItem { private final EventHandler eventHandler; private final Pattern[] topicPatterns; private static final Pattern WILDCARD_PATTERN = Pattern.compile("[^*]+|(\\*)"); public EventHandlerItem(EventHandler eventHandler, Map<String, Object> props) { this.eventHandler = eventHandler; topicPatterns = generateTopicPatterns(props.get(EventConstants.EVENT_TOPIC)); } public boolean matches(Event event) { if (topicPatterns.length == 0) { return true; } String topic = event.getTopic(); if (topic != null) { for (Pattern topicPattern : topicPatterns) { if (topicPattern.matcher(topic).matches()) { return true; } } } return false; } public EventHandler getEventHandler() { return eventHandler; } private static Pattern[] generateTopicPatterns(Object topic) { String[] topics; if (topic == null) { topics = new String[0]; } else if (topic instanceof String) { topics = new String[] { (String)topic }; } else if (topic instanceof String[]) { topics = (String[])topic; } else { throw new IllegalArgumentException("Invalid topic: " + topic); } Pattern[] patterns = new Pattern[topics.length]; for (int i=0; i<topics.length; i++) { patterns[i] = toWildcardPattern(topics[i]); } return patterns; } /** * Converts a wildcard string with * to a regex pattern (from http://stackoverflow.com/questions/24337657/wildcard-matching-in-java) * @param wildcard * @return Regexp pattern */ private static Pattern toWildcardPattern(String wildcard) { Matcher matcher = WILDCARD_PATTERN.matcher(wildcard); StringBuffer result = new StringBuffer(); while (matcher.find()) { if(matcher.group(1) != null) matcher.appendReplacement(result, ".*"); else matcher.appendReplacement(result, "\\\\Q" + matcher.group(0) + "\\\\E"); } matcher.appendTail(result); return Pattern.compile(result.toString()); } } }