/**
* 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.isis.core.runtime.services.eventbus;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import com.google.common.base.Strings;
import org.apache.isis.applib.NonRecoverableException;
import org.apache.isis.applib.annotation.Programmatic;
import org.apache.isis.applib.services.eventbus.EventBusImplementation;
import org.apache.isis.applib.services.eventbus.EventBusService;
import org.apache.isis.applib.services.registry.ServiceRegistry2;
import org.apache.isis.core.commons.lang.ClassUtil;
import org.apache.isis.core.metamodel.facets.Annotations;
import org.apache.isis.core.runtime.services.RequestScopedService;
import org.apache.isis.core.runtime.services.eventbus.adapter.EventBusImplementationForAxonSimple;
import org.apache.isis.core.runtime.services.eventbus.adapter.EventBusImplementationForGuava;
/**
* Holds common runtime logic for EventBusService implementations.
*/
public abstract class EventBusServiceDefault extends EventBusService {
public static final String KEY_ALLOW_LATE_REGISTRATION = "isis.services.eventbus.allowLateRegistration";
public static final String KEY_EVENT_BUS_IMPLEMENTATION = "isis.services.eventbus.implementation";
//region > register
/**
* {@inheritDoc}
*
* This service overrides the method to perform additional validation that (a) request-scoped services register
* their proxies, not themselves, and (b) that singleton services are never registered after the event bus has
* been created.
*
* <p>
* Note that we <i>do</i> allow for request-scoped services to register (their proxies) multiple times, ie at
* the beginning of each transaction. Because the subscribers are stored in a set, these additional
* registrations are in effect ignored.
* </p>
*/
@Override
public void register(final Object domainService) {
if(domainService instanceof RequestScopedService) {
// ok; allow to be registered multiple times (each xactn) since stored in a set.
} else {
if (Annotations.getAnnotation(domainService.getClass(), RequestScoped.class) != null) {
throw new IllegalArgumentException("Request-scoped services must register their proxy, not themselves");
}
// a singleton
if (!allowLateRegistration && hasPosted()) {
// ... coming too late to the party.
throw new IllegalStateException("Attempting to register '" + domainService.getClass().getSimpleName() + "' as a subscriber. However events have already been posted and it is too late to register any further (singleton) subscribers. Either use @DomainServiceLayout(menuOrder=...) on subscribing services to ensure that subscribers are initialized before any services that might post events, or alternatively use '" + KEY_ALLOW_LATE_REGISTRATION + "' configuration property to relax this check (meaning that some subscribers will miss some posted events)");
}
}
super.register(domainService);
}
//endregion
//region > init, shutdown
@Programmatic
@PostConstruct
public void init(final Map<String, String> properties) {
this.allowLateRegistration = getElseFalse(properties, KEY_ALLOW_LATE_REGISTRATION);
this.implementation = getNormalized(properties.get(KEY_EVENT_BUS_IMPLEMENTATION));
}
private static String getNormalized(final String implementation) {
if(Strings.isNullOrEmpty(implementation)) {
return "guava";
} else {
final String implementationTrimmed = implementation.trim();
if("guava".equalsIgnoreCase(implementationTrimmed)) {
return "guava";
} else if("axon".equalsIgnoreCase(implementationTrimmed)) {
return "axon";
} else {
return implementationTrimmed;
}
}
}
private static boolean getElseFalse(final Map<String, String> properties, final String key) {
final String value = properties.get(key);
return !Strings.isNullOrEmpty(value) && Boolean.parseBoolean(value);
}
//endregion
private boolean allowLateRegistration;
boolean isAllowLateRegistration() {
return allowLateRegistration;
}
/**
* Either <guava> or <axon>, or else the fully qualified class name of an
* implementation of {@link org.apache.isis.applib.services.eventbus.EventBusImplementation}.
*/
private String implementation;
String getImplementation() {
return implementation;
}
@Override
protected org.apache.isis.applib.services.eventbus.EventBusImplementation newEventBus() {
final EventBusImplementation implementation = instantiateEventBus();
serviceRegistry2.injectServicesInto(implementation);
return implementation;
}
private EventBusImplementation instantiateEventBus() {
if("guava".equals(implementation)) {
return new EventBusImplementationForGuava();
}
if("axon".equals(implementation)) {
return new EventBusImplementationForAxonSimple();
}
final Class<?> aClass = ClassUtil.forName(implementation);
if(EventBusImplementation.class.isAssignableFrom(aClass)) {
try {
return (EventBusImplementation) aClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new NonRecoverableException(e);
}
}
throw new NonRecoverableException(
"Could not instantiate event bus implementation '" + implementation + "'");
}
//endregion
@javax.inject.Inject
ServiceRegistry2 serviceRegistry2;
}