blob: fa16f447c52a041db491cd713f916c6d73019257 [file] [log] [blame]
/*
* 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());
}
}
}