auf spring ApplicationListener umgebaut.

Die Mixer bekommen nun DeviceChangedEvents wenn sich ein Gerät updatet.
This commit is contained in:
Lucas Pleß 2012-03-03 13:00:53 +01:00
parent 7d2ab0dd44
commit d8817c2e46
18 changed files with 224 additions and 268 deletions

View File

@ -0,0 +1,34 @@
package de.ctdo.bunti;
import java.util.Map;
import org.springframework.context.ApplicationEvent;
import de.ctdo.bunti.model.BuntiDevice;
public class DeviceChangedEvent extends ApplicationEvent {
private static final long serialVersionUID = 104525838879412827L;
private BuntiDevice device;
private Map<String, Object> options;
public DeviceChangedEvent(Object source, BuntiDevice device, Map<String, Object> options) {
super(source);
this.device = device;
this.options = options;
}
public BuntiDevice getDevice() {
return device;
}
public Map<String, Object> getOptions() {
return options;
}
@Override
public String toString() {
return "DeviceChangedEvent " + getDevice().getDeviceName();
}
}

View File

@ -1,11 +0,0 @@
package de.ctdo.bunti;
import java.util.Map;
import de.ctdo.bunti.model.BuntiDevice;
public interface Mixer {
public boolean setDevice(BuntiDevice device, Map<String, Object> options);
}

View File

@ -1,5 +0,0 @@
package de.ctdo.bunti.control;
public interface BroadcastListener {
void Broadcast(String message);
}

View File

@ -5,49 +5,26 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import net.sf.json.JSONObject;
import de.ctdo.bunti.DeviceChangedEvent;
import de.ctdo.bunti.dao.BuntiDevicesDAO;
import de.ctdo.bunti.devices.*;
import de.ctdo.bunti.dmx.*;
import de.ctdo.bunti.model.*;
@Component
public class BuntiControllerImpl implements BuntiController {
Logger logger = LoggerFactory.getLogger(getClass());
BuntiDevicesDAO devicesDAO;
DMXMixer dmxMixer;
DeviceMixer deviceMixer;
// protected final List<BroadcastListener> listeners = new ArrayList<BroadcastListener>();
@Autowired
public void setDmxMixer(DMXMixer dmxMixer) {
this.dmxMixer = dmxMixer;
}
@Autowired
public void setDeviceMixer(DeviceMixer deviceMixer) {
this.deviceMixer = deviceMixer;
}
public class BuntiControllerImpl implements BuntiController, ApplicationEventPublisherAware {
private Logger logger = LoggerFactory.getLogger(getClass());
private ApplicationEventPublisher applicationEventPublisher = null;
private BuntiDevicesDAO devicesDAO;
@Autowired
public void setDevicesDAO(BuntiDevicesDAO devicesDAO) {
this.devicesDAO = devicesDAO;
}
// public void addListener(BroadcastListener l) {
// synchronized (listeners) {
// listeners.add(l);
// }
// }
// public void removeListener(BroadcastListener l) {
// synchronized (listeners) {
// listeners.remove(l);
// }
// }
public void performJSONString(String json) {
JSONObject jsonobj = JSONObject.fromObject(json);
@ -71,13 +48,10 @@ public class BuntiControllerImpl implements BuntiController {
if (device != null) {
//TODO hier dann DeviceChangedEvent feuern
this.applicationEventPublisher.publishEvent(new DeviceChangedEvent(this, device, options));
logger.debug("publishEvent in BuntiController");
if (device instanceof BuntiDMXDevice) {
dmxMixer.setDevice(device, options);
} else if (device instanceof BuntiSwitchingDevice) {
deviceMixer.setDevice(device, options);
}
return true;
}
@ -86,6 +60,11 @@ public class BuntiControllerImpl implements BuntiController {
return false;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.applicationEventPublisher = publisher;
}
// public void sendBroadcastMessage(String message) {
// for (BroadcastListener l : listeners) {
// l.Broadcast(message);

View File

@ -12,8 +12,8 @@ import de.ctdo.bunti.model.*;
@Component
public class BuntiDevicesDAOImpl implements BuntiDevicesDAO {
Logger logger = LoggerFactory.getLogger(getClass());
List<BuntiDevice> devices = new ArrayList<BuntiDevice>();
private Logger logger = LoggerFactory.getLogger(getClass());
private List<BuntiDevice> devices = new ArrayList<BuntiDevice>();
public BuntiDevicesDAOImpl() {
addDummyDevices();
@ -30,7 +30,7 @@ public class BuntiDevicesDAOImpl implements BuntiDevicesDAO {
devices.add(new Strobe1500(deviceID++, 21, "Stroboskop 1"));
devices.add(new Par56Spot(deviceID++, 508, "Par56 Lampe 5"));
logger.debug("added dummy devices in DAO");
}
@ -48,7 +48,7 @@ public class BuntiDevicesDAOImpl implements BuntiDevicesDAO {
@Override
public BuntiDevice getDeviceById(int deviceId) {
for (BuntiDevice dev : devices) {
if(dev.getDeviceID() == deviceId) {
if(dev.getDeviceId() == deviceId) {
return dev;
}
}

View File

@ -1,9 +1,11 @@
package de.ctdo.bunti.devices;
import de.ctdo.bunti.Mixer;
import org.springframework.context.ApplicationListener;
public interface DeviceMixer extends Mixer {
import de.ctdo.bunti.DeviceChangedEvent;
public interface DeviceMixer extends ApplicationListener<DeviceChangedEvent> {

View File

@ -1,24 +1,27 @@
package de.ctdo.bunti.devices;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import de.ctdo.bunti.model.BuntiDMXDevice;
import de.ctdo.bunti.model.BuntiDevice;
import de.ctdo.bunti.DeviceChangedEvent;
import de.ctdo.bunti.model.BuntiSwitchingDevice;
@Component
public class DeviceMixerImpl implements DeviceMixer {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public boolean setDevice(BuntiDevice device, Map<String, Object> options) {
BuntiDMXDevice dmxDev = (BuntiDMXDevice)device;
return dmxDev.setValuesFromOptions(options);
}
public void onApplicationEvent(DeviceChangedEvent arg0) {
if( arg0.getDevice() instanceof BuntiSwitchingDevice) {
BuntiSwitchingDevice switchDev = (BuntiSwitchingDevice)arg0.getDevice();
switchDev.setValuesFromOptions(arg0.getOptions());
}
}
}

View File

@ -7,13 +7,27 @@ public class DMX {
public static int DMX_CHANNEL_VALUE_MAX = 255;
public static int DMX_CHANNEL_VALUE_MIN = 0;
/**
* Offset by which startaddress differs from DMX512 Data Array location
*/
public static int DMX_STARTADDRESS_OFFSET = -1;
/**
* Checks the DMX Value boundaries
* @param value
* @return A valid DMX512 channel value
*/
public static int sanitizeDMXValue(int value) {
if(value < DMX_CHANNEL_VALUE_MIN) value = DMX_CHANNEL_VALUE_MIN;
if(value > DMX_CHANNEL_VALUE_MAX) value = DMX_CHANNEL_VALUE_MAX;
return value;
}
/**
* Checks the DMX Channel boundaries
* @param channel The channel to check
* @return True if channel is valid. Otherwise false.
*/
public static boolean checkChannelBoundaries(int channel) {
return (channel >= DMX_CHANNELS_MIN && channel <= DMX_CHANNELS_MAX);
}

View File

@ -5,8 +5,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
/**
* DMXChannel container
*/
@ -15,12 +13,6 @@ public class DMXChannels {
protected Map<Integer,DMXChannel> channelByNumber = new HashMap<Integer, DMXChannel>();
protected Map<String,DMXChannel> channelByName = new HashMap<String, DMXChannel>();
/**
* Creates a new DMXChannel container
*/
public DMXChannels() {
}
/**
* Returns the number of channels
* @return number of channels
@ -41,14 +33,14 @@ public class DMXChannels {
return this.channelByName.get(name);
}
/**
* Returns a channel by offset
* @param number number
* @return channel or null if not found
*/
public DMXChannel getChannelByNumber(int number) {
return this.channelByNumber.get(number);
}
// /**
// * Returns a channel by offset
// * @param number number
// * @return channel or null if not found
// */
// public DMXChannel getChannelByNumber(int number) {
// return this.channelByNumber.get(number);
// }
/**
* Adds a channel

View File

@ -1,7 +0,0 @@
package de.ctdo.bunti.dmx;
public interface DMXDataChangedListener {
void DMXDataChanged(int[] dmx512data);
}

View File

@ -7,26 +7,28 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import de.ctdo.bunti.DeviceChangedEvent;
import de.ctdo.bunti.dao.BuntiDevicesDAO;
import de.ctdo.bunti.model.*;
@Component
public class DMXMixerImpl implements DMXMixer {
Logger logger = LoggerFactory.getLogger(getClass());
final int TICKS_BETWEEN_DMX_SEND = 20;
final String ARTNET_DEVICE_ADDRESS = "192.168.0.90";
private Logger logger = LoggerFactory.getLogger(getClass());
private final int TICKS_BETWEEN_DMX_SEND = 20;
// private final String ARTNET_DEVICE_ADDRESS = "192.168.0.90";
int[] dmx512databuffer = new int[DMX.DMX_CHANNELS_MAX+1];
private int[] dmx512databuffer = new int[DMX.DMX_CHANNELS_MAX+1];
static int sequenceID = 0;
long ticksLastBufferFlush = 0;
BuntiDevicesDAO devicesDAO;
// private int sequenceID = 0;
private long ticksLastBufferFlush = 0;
private BuntiDevicesDAO devicesDAO;
@Autowired
public void setDevicesDAO(BuntiDevicesDAO devicesDAO) {
this.devicesDAO = devicesDAO;
}
private void sendOutDMXBuffer() {
StringBuilder sb = new StringBuilder();
@ -41,35 +43,50 @@ public class DMXMixerImpl implements DMXMixer {
sb.append(", ");
}
// sequenceID++;
logger.debug(sb.toString());
}
public void sendOutDMXBufferIfNeeded() {
if(ticksLastBufferFlush + TICKS_BETWEEN_DMX_SEND < System.currentTimeMillis()) {
mergeDevicesIntoBuffer();
// mergeDevicesIntoBuffer();
sendOutDMXBuffer();
ticksLastBufferFlush = System.currentTimeMillis();
}
}
private void mergeDevicesIntoBuffer() {
private void updateDevice(BuntiDevice device, Map<String, Object> options) {
BuntiDMXDevice dmxDev = (BuntiDMXDevice)device;
boolean retval = dmxDev.setValuesFromOptions(options);
for (BuntiDMXDevice buntiDMXDevice : devicesDAO.getAllDMXDevices()) {
long lastchanged = buntiDMXDevice.getLastChanged();
long lastSend = buntiDMXDevice.getLastSendOut();
if( retval ) {
dmxDev.mergeDMXData(dmx512databuffer);
if(lastchanged >= lastSend ) {
buntiDMXDevice.mergeDMXData(dmx512databuffer);
logger.debug("merged " + buntiDMXDevice + " into dmx buffer");
buntiDMXDevice.setSendOutNow();
}
sendOutDMXBufferIfNeeded();
} else {
logger.error("setValuesFromOptions failed");
}
}
// private void mergeDevicesIntoBuffer() {
//
// for (BuntiDMXDevice buntiDMXDevice : devicesDAO.getAllDMXDevices()) {
// long lastchanged = buntiDMXDevice.getLastChanged();
// long lastSend = buntiDMXDevice.getLastSendOut();
//
// if (lastchanged >= lastSend) {
// buntiDMXDevice.mergeDMXData(dmx512databuffer);
// logger.debug("merged " + buntiDMXDevice + " into dmx buffer");
// buntiDMXDevice.setSendOutNow();
// }
//
// }
// }
// public void setDMX512Channel(int channel, int value) {
// if(!DMX.checkChannelBoundaries(channel)) return;
// value = DMX.sanitizeDMXValue(value);
@ -80,20 +97,16 @@ public class DMXMixerImpl implements DMXMixer {
// }
@Override
public boolean setDevice(BuntiDevice device, Map<String, Object> options) {
boolean retval = false;
public void onApplicationEvent(DeviceChangedEvent arg0) {
if( device instanceof BuntiDMXDevice) {
if( arg0.getDevice() instanceof BuntiDMXDevice) {
BuntiDMXDevice dmxDev = (BuntiDMXDevice)device;
retval = dmxDev.setValuesFromOptions(options);
updateDevice(arg0.getDevice(), arg0.getOptions());
if( retval ) {
sendOutDMXBufferIfNeeded();
}
}
return retval;
}
}

View File

@ -1,21 +1,19 @@
package de.ctdo.bunti.model;
import java.util.*;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.ctdo.bunti.dmx.*;
public abstract class BuntiDMXDevice extends BuntiDevice {
Logger logger = LoggerFactory.getLogger(getClass());
int startAddress;
long lastSendOut;
DMXChannels dmxChannels = new DMXChannels();
protected int startAddress;
private long lastSendOut;
protected DMXChannels dmxChannels = new DMXChannels();
public int getStartAddress() {
return startAddress+1;
public BuntiDMXDevice(int deviceId, int startAddress, String name) {
this.startAddress = startAddress;
this.deviceName = name;
this.deviceId = deviceId;
}
public long getLastSendOut() {
@ -26,25 +24,20 @@ public abstract class BuntiDMXDevice extends BuntiDevice {
this.lastSendOut = System.currentTimeMillis();
}
public int getStartAddress() {
return startAddress;
}
public void setStartAddress(int startAddress) {
this.startAddress = startAddress;
}
/**
* set the DMX Start Address (1 to 512)
* @param startAddress
* Set channel value by given channel name.
* @param name The channel name to change the value.
* @param value The channel value to set.
*/
public void setStartAddress(int startAddress) {
this.startAddress = startAddress-1;
}
public void addChannel(DMXChannel channel) {
//dmxChannels.add(channel);
dmxChannels.addChannel(channel);
}
public void setChannelValueByName(String name, int value) {
protected void setChannelValueByName(String name, int value) {
DMXChannel dx = dmxChannels.getChannelByName(name);
if(dx != null) {
dx.setValue(DMX.sanitizeDMXValue(value));
@ -52,7 +45,12 @@ public abstract class BuntiDMXDevice extends BuntiDevice {
lastChangedNow();
}
public int getChannelValueByName(String name) {
/**
* Returns the channel value identified by channel name.
* @param name The channel name to get the value from.
* @return The desired channel value.
*/
protected int getChannelValueByName(String name) {
DMXChannel dx = dmxChannels.getChannelByName(name);
if(dx != null) {
return dx.getValue();
@ -60,25 +58,21 @@ public abstract class BuntiDMXDevice extends BuntiDevice {
return 0;
}
/**
* Merge the DMX values from this device into a global DMX512 Data Array
* @param dmxData
* @return
* Merge the DMX values from this device into a global DMX512 Data Array.
* @param dmxData DMX512 Data array to merge the local values into.
* @return True on success, false otherwise.
*/
public boolean mergeDMXData(int[] dmxData) {
if(dmxData == null) {
if(dmxData == null || dmxData.length == 0) {
return false;
}
for (DMXChannel channel : dmxChannels.getAllChannels()) {
int index = channel.getOffset() + startAddress;
int index = channel.getOffset() + (startAddress - DMX.DMX_STARTADDRESS_OFFSET);
if(index >= DMX.DMX_CHANNEL_VALUE_MIN && index <= DMX.DMX_CHANNELS_MAX){
if(index >= 0 && index < dmxData.length){
dmxData[index] = channel.getValue();
} else {
return false;
@ -88,17 +82,6 @@ public abstract class BuntiDMXDevice extends BuntiDevice {
return true;
}
@Override
public void switchOff() {
}
@Override
public void switchOn() {
}
@Override
public boolean setValuesFromOptions(Map<String, Object> options) {
@ -120,43 +103,8 @@ public abstract class BuntiDMXDevice extends BuntiDevice {
return true;
}
// public void setChannelValueByOffset(int offsetChannel, int value) {
// DMXChannel dx = getDMXChannelByOffset(offsetChannel);
// if(dx != null) {
// dx.value = DMX.sanitizeDMXValue(value);
// }
//}
//public void setChannelValueByAddress(int address, int value) {
// DMXChannel dx = getDMXChannelByAddress(address);
// if(dx != null) {
// dx.value = DMX.sanitizeDMXValue(value);
// }
//}
//public int getChannelValueByOffset(int offsetChannel) {
// DMXChannel dx = getDMXChannelByOffset(offsetChannel)
// if(dx != null) {
// return dx.value;
// }
// return 0;
//}
//public int getChannelValueByAddress(int address) {
// DMXChannel dx = getDMXChannelByAddress(address);
// if(dx != null) {
// return dx.value;
// }
// return 0;
//}
@Override
public String toString() {
return "BuntiDMXDevice " + getDeviceID() + ", " + getDeviceName();
return "BuntiDMXDevice " + deviceId + ", " + deviceName;
}
}

View File

@ -3,37 +3,45 @@ package de.ctdo.bunti.model;
import java.util.Map;
public abstract class BuntiDevice {
protected int deviceId;
protected String deviceName;
private long lastChanged;
int deviceID;
String deviceName;
long lastChanged;
public int getDeviceId() {
return deviceId;
}
public int getDeviceID() {
return deviceID;
}
public void setDeviceID(int deviceID) {
this.deviceID = deviceID;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public long getLastChanged() {
return lastChanged;
}
public void setLastChanged(long lastChanged) {
this.lastChanged = lastChanged;
}
protected void lastChangedNow() {
this.lastChanged = System.currentTimeMillis();
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
/**
* Switch this device off.
*/
public abstract void switchOff();
/**
* Switch this device on.
*/
public abstract void switchOn();
/**
* The the internal options corresponding to the given Key Value Map
* @param options The options Map.
* @return True on success. False otherwise.
*/
public abstract boolean setValuesFromOptions(Map<String, Object> options);
}

View File

@ -5,21 +5,20 @@ import de.ctdo.bunti.dmx.DMXChannel;
public class Par56Spot extends BuntiDMXDevice {
public final static String CHANNEL_MODE = "mode";
public final static String CHANNEL_RED = "red";
public final static String CHANNEL_GREEN = "green";
public final static String CHANNEL_BLUE = "blue";
public final static String CHANNEL_SPEED = "speed";
private final static String CHANNEL_MODE = "mode";
private final static String CHANNEL_RED = "red";
private final static String CHANNEL_GREEN = "green";
private final static String CHANNEL_BLUE = "blue";
private final static String CHANNEL_SPEED = "speed";
public Par56Spot(int deviceId, int startAddress, String name) {
addChannel(new DMXChannel(0, CHANNEL_MODE));
addChannel(new DMXChannel(1, "red"));
addChannel(new DMXChannel(2, CHANNEL_GREEN));
addChannel(new DMXChannel(3, CHANNEL_BLUE));
addChannel(new DMXChannel(4, CHANNEL_SPEED));
setStartAddress(startAddress);
setDeviceName(name);
setDeviceID(deviceId);
public Par56Spot(int deviceId, int startAddress, String deviceName) {
super(deviceId, startAddress, deviceName);
dmxChannels.addChannel(new DMXChannel(0, CHANNEL_MODE));
dmxChannels.addChannel(new DMXChannel(1, "red"));
dmxChannels.addChannel(new DMXChannel(2, CHANNEL_GREEN));
dmxChannels.addChannel(new DMXChannel(3, CHANNEL_BLUE));
dmxChannels.addChannel(new DMXChannel(4, CHANNEL_SPEED));
}
public void setColorRed(int value) {
@ -31,7 +30,6 @@ public class Par56Spot extends BuntiDMXDevice {
public void setColorBlue(int value) {
setChannelValueByName(CHANNEL_BLUE, value);
}
public int getColorRed() {
return getChannelValueByName(CHANNEL_RED);
}
@ -62,7 +60,7 @@ public class Par56Spot extends BuntiDMXDevice {
@Override
public String toString() {
return "Par56Spot " + getDeviceID() + ", " + getDeviceName() +
return "Par56Spot " + deviceId + ", " + deviceName + " " +
"[" + getColorRed() + "," + getColorGreen() + "," + getColorBlue() + "]";
}

View File

@ -5,17 +5,16 @@ import de.ctdo.bunti.dmx.DMXChannel;
public class Strobe1500 extends BuntiDMXDevice {
public final static String CHANNEL_SPEED = "speed";
public final static String CHANNEL_INTENSITY = "intensity";
public final static String CHANNEL_MODE = "mode";
private final static String CHANNEL_SPEED = "speed";
private final static String CHANNEL_INTENSITY = "intensity";
private final static String CHANNEL_MODE = "mode";
public Strobe1500(int deviceId, int startAddress, String name) {
addChannel(new DMXChannel(0, CHANNEL_SPEED));
addChannel(new DMXChannel(1, CHANNEL_INTENSITY));
addChannel(new DMXChannel(2, CHANNEL_MODE));
setStartAddress(startAddress);
setDeviceName(name);
setDeviceID(deviceId);
public Strobe1500(int deviceId, int startAddress, String deviceName) {
super(deviceId, startAddress, deviceName);
this.dmxChannels.addChannel(new DMXChannel(0, CHANNEL_SPEED));
this.dmxChannels.addChannel(new DMXChannel(1, CHANNEL_INTENSITY));
this.dmxChannels.addChannel(new DMXChannel(2, CHANNEL_MODE));
}
public void setSpeed(int value) {
@ -27,7 +26,6 @@ public class Strobe1500 extends BuntiDMXDevice {
public void setMode(int value) {
setChannelValueByName(CHANNEL_MODE, value);
}
public int getSpeed() {
return getChannelValueByName(CHANNEL_SPEED);
}
@ -54,7 +52,7 @@ public class Strobe1500 extends BuntiDMXDevice {
@Override
public String toString() {
return "Strobe1500 " + getDeviceID() + ", " + getDeviceName() +
return "Strobe1500 " + deviceId + ", " + deviceName +
"[" + getSpeed() + "," + getIntensity() + "," + getMode() + "]";
}

View File

@ -15,7 +15,7 @@ import de.ctdo.bunti.control.BuntiController;
@Controller
public class TestController {
Logger logger = LoggerFactory.getLogger(getClass());
private Logger logger = LoggerFactory.getLogger(getClass());
BuntiController buntiController;
@Autowired
@ -25,8 +25,6 @@ public class TestController {
@RequestMapping("/foobar")
public ModelAndView blafasel() {
Map<String,Object> options = new HashMap<String,Object>();
options.put("red", 124);

View File

@ -7,14 +7,12 @@ import com.sun.grizzly.websockets.DefaultWebSocket;
import com.sun.grizzly.websockets.ProtocolHandler;
import com.sun.grizzly.websockets.WebSocketListener;
import de.ctdo.bunti.control.BroadcastListener;
/**
* Ein DMXControllerWebSocket gehoert immer zu einem Browserfenster/Tab
* @author lucas
*
*/
public class BuntiControllerWebSocket extends DefaultWebSocket implements BroadcastListener {
public class BuntiControllerWebSocket extends DefaultWebSocket {
Logger logger = LoggerFactory.getLogger(BuntiControllerWebSocket.class);
@ -22,11 +20,6 @@ public class BuntiControllerWebSocket extends DefaultWebSocket implements Broadc
super(protocolHandler, listeners);
}
@Override
public void Broadcast(String message) {
// TODO Auto-generated method stub
}
// @Override
// public void DMXDataChanged(int[] dmx512data) {

View File

@ -12,5 +12,4 @@
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
</beans>