de.mhus.osgi.jms.services.JmsManagerServiceImpl Maven / Gradle / Ivy
/**
* Copyright (C) 2020 Mike Hummel ([email protected])
*
* 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 de.mhus.osgi.jms.services;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.WeakHashMap;
import javax.jms.JMSException;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
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.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import de.mhus.lib.core.MLog;
import de.mhus.lib.core.MTimerTask;
import de.mhus.lib.core.service.TimerFactory;
import de.mhus.lib.core.service.TimerIfc;
import de.mhus.lib.errors.NotFoundException;
import de.mhus.lib.jms.JmsConnection;
import de.mhus.osgi.api.MOsgi;
import de.mhus.osgi.api.MOsgi.Service;
import de.mhus.osgi.api.jms.JmsDataChannel;
import de.mhus.osgi.api.jms.JmsDataSource;
import de.mhus.osgi.api.jms.JmsManagerService;
/**
* Note: because of a 'new behavior' or bug in Felix we need to delay the start of the service
* trackers. Immediately will cause an 'Circular reference detected' exception.
*
* Discussion: https://github.com/eclipse/smarthome/issues/870
*
*
Switching to DS driven references was also not successful, got the same exception.
*
*
You can chnage the behavior in mhus-config by setting JmsManagerService/startupDelay to zero
*
* @author mikehummel
*/
@Component(name = "JmsManagerService", immediate = true)
public class JmsManagerServiceImpl extends MLog implements JmsManagerService {
static JmsManagerService instance;
private ServiceTracker connectionTracker;
private ServiceTracker channelTracker;
private WeakHashMap channels = new WeakHashMap<>();
private HashSet connectionNames = new HashSet<>();
private BundleContext context;
private TimerIfc timer;
private MTimerTask timerTask;
protected boolean enabled = true;
@Activate
public void doActivate(ComponentContext ctx) {
instance = this;
// decouple starting of tracker from bundle activation
// this could cause a circular reference call
MOsgi.runAfterActivation(ctx, this::doStart);
}
public void doStart(ComponentContext ctx) {
context = ctx.getBundleContext();
connectionTracker =
new ServiceTracker<>(
context, JmsDataSource.class, new MyConnectionTrackerCustomizer());
connectionTracker.open();
channelTracker =
new ServiceTracker<>(
context, JmsDataChannel.class, new MyChannelTrackerCustomizer());
channelTracker.open();
}
@Deactivate
public void doDeactivate(ComponentContext ctx) {
if (timer != null) timer.cancel();
if (channelTracker != null) channelTracker.close();
if (connectionTracker != null) connectionTracker.close();
for (String name : new LinkedList(channels.keySet())) removeChannel(name);
instance = null;
}
@Reference(service = TimerFactory.class)
public synchronized void setTimerFactory(TimerFactory factory) {
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
log().i("create timer");
timer = factory.getTimer();
timerTask =
new MTimerTask() {
@Override
public void doit() throws Exception {
if (!enabled) return;
// check connections and channels
doBeat();
// reconnect
doChannelBeat();
}
};
timer.schedule(timerTask, 60000, 60000);
}
@Override
public void doBeat() {
// check connections
for (JmsDataSource con : MOsgi.getServices(JmsDataSource.class, null)) {
String name = con.getName();
if (name == null) continue;
if (!connectionNames.contains(name)) {
try {
addConnection(name, con.getConnection());
} catch (Throwable e) {
log().d(name, e);
}
}
}
// check the channels
for (JmsDataChannel c : MOsgi.getServices(JmsDataChannel.class, null)) {
String name = c.getName();
if (name == null) continue;
if (!channels.containsKey(name)) {
try {
addChannel(c);
} catch (Throwable e) {
log().d(name, e);
}
}
}
}
@Override
public List getConnections() {
LinkedList out = new LinkedList<>();
for (JmsDataSource obj : MOsgi.getServices(JmsDataSource.class, null))
try {
out.add(obj.getConnection());
} catch (JMSException e) {
log().w(e);
}
return out;
}
@Override
public List> getDataSources() {
LinkedList> out = new LinkedList<>();
for (MOsgi.Service obj : MOsgi.getServiceRefs(JmsDataSource.class, null))
out.add(obj);
return out;
}
@Override
public JmsConnection getConnection(String name) {
if (name == null) return null;
try {
JmsDataSource src =
MOsgi.getService(
JmsDataSource.class, "(osgi.jndi.service.name=jms_" + name + ")");
if (src == null) return null;
return src.getConnection();
} catch (NotFoundException nfe) {
for (Service c : getDataSources())
if (c.getService() != null && name.equals(c.getService().getName()))
try {
return c.getService().getConnection();
} catch (JMSException e) {
log().w(name, e);
}
return null;
} catch (JMSException e) {
log().w(name, e);
return null;
}
}
@Override
public void resetChannels() {
synchronized (channels) {
List channels = getChannels();
for (JmsDataChannel channel : channels)
try {
channel.reset();
} catch (Throwable t) {
log().t(channel.getName(), t);
}
}
}
private class MyConnectionTrackerCustomizer
implements ServiceTrackerCustomizer {
@Override
public JmsDataSource addingService(ServiceReference reference) {
JmsDataSource service = context.getService(reference);
try {
addConnection(service.getName(), service.createConnection());
} catch (JMSException e) {
log().t(e);
}
resetChannels();
return service;
}
@Override
public void modifiedService(
ServiceReference reference, JmsDataSource service) {
removeConnection(service.getName());
try {
addConnection(service.getName(), service.createConnection());
} catch (JMSException e) {
log().t(e);
}
resetChannels();
}
@Override
public void removedService(
ServiceReference reference, JmsDataSource service) {
removeConnection(service.getName());
resetChannels();
}
}
private class MyChannelTrackerCustomizer
implements ServiceTrackerCustomizer {
@Override
public JmsDataChannel addingService(ServiceReference reference) {
try {
JmsDataChannel service = context.getService(reference);
if (service != null) addChannel(service);
return service;
} catch (Throwable t) {
log().e("add serice failed", reference, t);
return null;
}
}
@Override
public void modifiedService(
ServiceReference reference, JmsDataChannel service) {
removeChannel(service.getName());
addChannel(service);
}
@Override
public void removedService(
ServiceReference reference, JmsDataChannel service) {
removeChannel(service.getName());
}
}
@Override
public JmsDataChannel getChannel(String name) {
try {
return MOsgi.getService(
JmsDataChannel.class, "(osgi.jndi.service.name=jmschannel_" + name + ")");
} catch (NotFoundException nfe) {
for (JmsDataChannel c : getChannels()) if (name.equals(c.getName())) return c;
return channels.get(name);
}
}
public void removeConnection(String name) {
if (name == null) return;
log().d("remove connection", name);
connectionNames.remove(name);
for (JmsDataChannel c : new LinkedList(channels.values()))
if (name.equals(c.getConnectionName()))
try {
c.onDisconnect();
} catch (Throwable t) {
log().w(name, c, t);
}
}
public void addConnection(String name, JmsConnection connection) {
if (name == null) return;
log().d("add connection", name);
connectionNames.add(name);
for (JmsDataChannel c : new LinkedList(channels.values()))
if (name.equals(c.getConnectionName()))
try {
c.onConnect();
} catch (Throwable t) {
log().w(name, c, t);
}
}
@Override
public List getChannels() {
LinkedList out = new LinkedList<>();
out.addAll(channels.values());
// for (JmsDataChannel obj : MOsgi.getServices(JmsDataChannel.class, null))
// out.add(obj);
// out.addAll(channels.values());
return out;
}
@Override
public void doChannelBeat() {
synchronized (channels) {
for (JmsDataChannel c : new LinkedList(channels.values()))
try {
c.doBeat();
} catch (Throwable t) {
log().d("channel beat failed", c, t);
}
}
}
@Override
public String getServiceName(Service ref) {
Object p = ref.getReference().getProperty("osgi.jndi.service.name");
if (p != null
&& p instanceof String
&& ((String) p).length() > 4
&& ((String) p).startsWith("jms_")) return ((String) p).substring(4);
return null;
}
@Override
public void addChannel(JmsDataChannel channel) {
log().d("add channel", channel.getName());
channels.put(channel.getName(), channel);
channel.onConnect();
}
@Override
public void removeChannel(String name) {
log().d("remove channel", name);
JmsDataChannel c = channels.remove(name);
if (c != null) c.onDisconnect();
}
@Override
public void resetConnection(String name) {
if (name == null) return;
log().d("reset connection", name);
for (JmsDataChannel c : new LinkedList(channels.values()))
if (name.equals(c.getConnectionName()))
try {
c.onDisconnect();
} catch (Throwable t) {
log().w(name, c, t);
}
}
}