/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Granite Data Services is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.config.flex;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.granite.config.api.Configuration;
import org.granite.logging.Logger;
import org.granite.messaging.service.annotations.RemoteDestination;
import org.granite.messaging.service.security.RemotingDestinationSecurizer;
import org.granite.scan.ScannedItem;
import org.granite.scan.ScannedItemHandler;
import org.granite.scan.Scanner;
import org.granite.scan.ScannerFactory;
import org.granite.util.TypeUtil;
import org.granite.util.XMap;
import org.xml.sax.SAXException;
import flex.messaging.messages.RemotingMessage;
/**
* @author Franck WOLFF
*/
public class ServicesConfig implements ChannelConfig, ScannedItemHandler {
///////////////////////////////////////////////////////////////////////////
// Fields.
private static final Logger log = Logger.getLogger(ServicesConfig.class);
private static final String SERVICES_CONFIG_PROPERTIES = "META-INF/services-config.properties";
private final Map<String, Service> services = new HashMap<String, Service>();
private final Map<String, Channel> channels = new HashMap<String, Channel>();
private final Map<String, Factory> factories = new HashMap<String, Factory>();
///////////////////////////////////////////////////////////////////////////
// Classpath scan initialization.
private void scanConfig(String serviceConfigProperties, List<ScannedItemHandler> handlers) {
Scanner scanner = ScannerFactory.createScanner(this, serviceConfigProperties != null ? serviceConfigProperties : SERVICES_CONFIG_PROPERTIES);
scanner.addHandlers(handlers);
try {
scanner.scan();
} catch (Exception e) {
log.error(e, "Could not scan classpath for configuration");
}
}
public boolean handleMarkerItem(ScannedItem item) {
return false;
}
public void handleScannedItem(ScannedItem item) {
if ("class".equals(item.getExtension()) && item.getName().indexOf('$') == -1) {
try {
handleClass(item.loadAsClass());
} catch (Throwable t) {
log.error(t, "Could not load class: %s", item);
}
}
}
public void handleClass(Class<?> clazz) {
RemoteDestination anno = clazz.getAnnotation(RemoteDestination.class);
if (anno != null && !("".equals(anno.id()))) {
XMap props = new XMap("properties");
// Owning service.
Service service = null;
if (anno.service().length() > 0) {
log.info("Configuring service from RemoteDestination annotation: service=%s (class=%s, anno=%s)", anno.service(), clazz, anno);
service = this.services.get(anno.service());
}
else if (this.services.size() > 0) {
// Lookup remoting service
log.info("Looking for service(s) with RemotingMessage type (class=%s, anno=%s)", clazz, anno);
int count = 0;
for (Service s : this.services.values()) {
if (RemotingMessage.class.getName().equals(s.getMessageTypes())) {
log.info("Found service with RemotingMessage type: service=%s (class=%s, anno=%s)", s, clazz, anno);
service = s;
count++;
}
}
if (count == 1 && service != null)
log.info("Service " + service.getId() + " selected for destination in class: " + clazz.getName());
else {
log.error("Found %d matching services (should be exactly 1, class=%s, anno=%s)", count, clazz, anno);
service = null;
}
}
if (service == null)
throw new RuntimeException("No service found for destination in class: " + clazz.getName());
// Channel reference.
List<String> channelIds = null;
if (anno.channels().length > 0)
channelIds = Arrays.asList(anno.channels());
else if (anno.channel().length() > 0)
channelIds = Collections.singletonList(anno.channel());
else if (this.channels.size() == 1) {
channelIds = new ArrayList<String>(this.channels.keySet());
log.info("Channel " + channelIds.get(0) + " selected for destination in class: " + clazz.getName());
}
else {
log.warn("No (or ambiguous) channel definition found for destination in class: " + clazz.getName());
channelIds = Collections.emptyList();
}
// Factory reference.
String factoryId = null;
if (anno.factory().length() > 0)
factoryId = anno.factory();
else if (this.factories.isEmpty()) {
props.put("scope", anno.scope());
props.put("source", clazz.getName());
log.info("Default POJO factory selected for destination in class: " + clazz.getName() + " with scope: " + anno.scope());
}
else if (this.factories.size() == 1) {
factoryId = this.factories.keySet().iterator().next();
log.info("Factory " + factoryId + " selected for destination in class: " + clazz.getName());
}
else
throw new RuntimeException("No (or ambiguous) factory definition found for destination in class: " + clazz.getName());
if (factoryId != null)
props.put("factory", factoryId);
if (!(anno.source().equals("")))
props.put("source", anno.source());
// Security roles.
List<String> roles = null;
if (anno.securityRoles().length > 0) {
roles = new ArrayList<String>(anno.securityRoles().length);
for (String role : anno.securityRoles())
roles.add(role);
}
// Securizer
if (anno.securizer() != RemotingDestinationSecurizer.class)
props.put("securizer", anno.securizer().getName());
Destination destination = new Destination(anno.id(), channelIds, props, roles, null, clazz);
service.getDestinations().put(destination.getId(), destination);
}
}
///////////////////////////////////////////////////////////////////////////
// Static ServicesConfig loaders.
public ServicesConfig(InputStream customConfigIs, Configuration configuration, boolean scan) throws IOException, SAXException {
if (customConfigIs != null)
loadConfig(customConfigIs);
if (scan)
scan(configuration);
}
public void scan(Configuration configuration) {
List<ScannedItemHandler> handlers = new ArrayList<ScannedItemHandler>();
for (Factory factory : factories.values()) {
try {
Class<?> clazz = TypeUtil.forName(factory.getClassName());
Method method = clazz.getMethod("getScannedItemHandler");
if ((Modifier.STATIC & method.getModifiers()) != 0 && method.getParameterTypes().length == 0) {
ScannedItemHandler handler = (ScannedItemHandler)method.invoke(null);
handlers.add(handler);
}
else
log.warn("Factory class %s contains an unexpected signature for method: %s", factory.getClassName(), method);
}
catch (NoSuchMethodException e) {
// ignore
}
catch (ClassNotFoundException e) {
log.error(e, "Could not load factory class: %s", factory.getClassName());
}
catch (Exception e) {
log.error(e, "Error while calling %s.getScannedItemHandler() method", factory.getClassName());
}
}
scanConfig(configuration != null ? configuration.getFlexServicesConfigProperties() : null, handlers);
}
private void loadConfig(InputStream configIs) throws IOException, SAXException {
XMap doc = new XMap(configIs);
forElement(doc);
}
///////////////////////////////////////////////////////////////////////////
// Services.
public Service findServiceById(String id) {
return services.get(id);
}
public List<Service> findServicesByMessageType(String messageType) {
List<Service> services = new ArrayList<Service>();
for (Service service : this.services.values()) {
if (messageType.equals(service.getMessageTypes()))
services.add(service);
}
return services;
}
public void addService(Service service) {
services.put(service.getId(), service);
}
///////////////////////////////////////////////////////////////////////////
// Channels.
public Channel findChannelById(String id) {
return channels.get(id);
}
public void addChannel(Channel channel) {
channels.put(channel.getId(), channel);
}
///////////////////////////////////////////////////////////////////////////
// Factories.
public Factory findFactoryById(String id) {
return factories.get(id);
}
public void addFactory(Factory factory) {
factories.put(factory.getId(), factory);
}
///////////////////////////////////////////////////////////////////////////
// Destinations.
public Destination findDestinationById(String messageType, String id) {
for (Service service : services.values()) {
if (messageType == null || messageType.equals(service.getMessageTypes())) {
Destination destination = service.findDestinationById(id);
if (destination != null)
return destination;
}
}
return null;
}
public List<Destination> findDestinationsByMessageType(String messageType) {
List<Destination> destinations = new ArrayList<Destination>();
for (Service service : services.values()) {
if (messageType.equals(service.getMessageTypes()))
destinations.addAll(service.getDestinations().values());
}
return destinations;
}
///////////////////////////////////////////////////////////////////////////
// Static helper.
private void forElement(XMap element) {
XMap services = element.getOne("services");
if (services != null) {
for (XMap service : services.getAll("service")) {
Service serv = Service.forElement(service);
this.services.put(serv.getId(), serv);
}
/* TODO: service-include...
for (Element service : (List<Element>)services.getChildren("service-include")) {
config.services.add(Service.forElement(service));
}
*/
}
XMap channels = element.getOne("channels");
if (channels != null) {
for (XMap channel : channels.getAll("channel-definition")) {
Channel chan = Channel.forElement(channel);
this.channels.put(chan.getId(), chan);
}
}
else {
log.info("No channel definition found, using defaults");
EndPoint defaultEndpoint = new EndPoint("http://{server.name}:{server.port}/{context.root}/graniteamf/amf", "flex.messaging.endpoints.AMFEndpoint");
Channel defaultChannel = new Channel("my-graniteamf", "mx.messaging.channels.AMFChannel", defaultEndpoint, XMap.EMPTY_XMAP);
this.channels.put(defaultChannel.getId(), defaultChannel);
}
XMap factories = element.getOne("factories");
if (factories != null) {
for (XMap factory : factories.getAll("factory")) {
Factory fact = Factory.forElement(factory);
this.factories.put(fact.getId(), fact);
}
}
}
/**
* Remove service (new addings for osgi).
* @param clazz service class.
*/
public void handleRemoveService(Class<?> clazz){
RemoteDestination anno = clazz.getAnnotation(RemoteDestination.class);
if(anno!=null){
Service service=null;
if (anno.service().length() > 0){
service=services.get(anno.service());
}
else if (services.size() > 0) {
// Lookup remoting service
for (Service s : services.values()) {
if (RemotingMessage.class.getName().equals(s.getMessageTypes())) {
service = s;
log.info("Service " + service.getId() + " selected for destination in class: " + clazz.getName());
break;
}
}
}
if(service!=null){
Destination dest=service.getDestinations().remove(anno.id());
if (dest != null) {
dest.remove();
log.info("RemoteDestination:"+dest.getId()+" has been removed");
}
}else{
log.info("Service NOT Found!!");
}
}
}
@Override
public boolean getChannelProperty(String channelId, String propertyName) {
if (channelId == null)
return false;
Channel channel = findChannelById(channelId);
if (channel == null) {
log.debug("Could not get channel for channel id: %s", channelId);
return false;
}
if ("legacyXmlSerialization".equals(propertyName))
return channel.isLegacyXmlSerialization();
else if ("legacyCollectionSerialization".equals(propertyName))
return channel.isLegacyCollectionSerialization();
return false;
}
}