001/*
002  GRANITE DATA SERVICES
003  Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004
005  This file is part of Granite Data Services.
006
007  Granite Data Services is free software; you can redistribute it and/or modify
008  it under the terms of the GNU Library General Public License as published by
009  the Free Software Foundation; either version 2 of the License, or (at your
010  option) any later version.
011
012  Granite Data Services is distributed in the hope that it will be useful, but
013  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015  for more details.
016
017  You should have received a copy of the GNU Library General Public License
018  along with this library; if not, see <http://www.gnu.org/licenses/>.
019*/
020
021package org.granite.config.flex;
022
023import java.io.IOException;
024import java.io.InputStream;
025import java.lang.reflect.Method;
026import java.lang.reflect.Modifier;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Map;
033
034import org.granite.config.api.Configuration;
035import org.granite.logging.Logger;
036import org.granite.messaging.service.annotations.RemoteDestination;
037import org.granite.messaging.service.security.RemotingDestinationSecurizer;
038import org.granite.scan.ScannedItem;
039import org.granite.scan.ScannedItemHandler;
040import org.granite.scan.Scanner;
041import org.granite.scan.ScannerFactory;
042import org.granite.util.TypeUtil;
043import org.granite.util.XMap;
044import org.xml.sax.SAXException;
045
046import flex.messaging.messages.RemotingMessage;
047
048
049/**
050 * @author Franck WOLFF
051 */
052public class ServicesConfig implements ScannedItemHandler {
053
054    ///////////////////////////////////////////////////////////////////////////
055    // Fields.
056
057    private static final Logger log = Logger.getLogger(ServicesConfig.class);
058    private static final String SERVICES_CONFIG_PROPERTIES = "META-INF/services-config.properties";
059
060    private final Map<String, Service> services = new HashMap<String, Service>();
061    private final Map<String, Channel> channels = new HashMap<String, Channel>();
062    private final Map<String, Factory> factories = new HashMap<String, Factory>();
063
064    
065    ///////////////////////////////////////////////////////////////////////////
066    // Classpath scan initialization.
067    
068    private void scanConfig(String serviceConfigProperties, List<ScannedItemHandler> handlers) {
069        Scanner scanner = ScannerFactory.createScanner(this, serviceConfigProperties != null ? serviceConfigProperties : SERVICES_CONFIG_PROPERTIES);
070        scanner.addHandlers(handlers);
071        try {
072            scanner.scan();
073        } catch (Exception e) {
074            log.error(e, "Could not scan classpath for configuration");
075        }
076    }
077
078    public boolean handleMarkerItem(ScannedItem item) {
079        return false;
080    }
081
082    public void handleScannedItem(ScannedItem item) {
083        if ("class".equals(item.getExtension()) && item.getName().indexOf('$') == -1) {
084            try {
085                handleClass(item.loadAsClass());
086            } catch (Throwable t) {
087                log.error(t, "Could not load class: %s", item);
088            }
089        }
090    }
091
092    public void handleClass(Class<?> clazz) {
093        RemoteDestination anno = clazz.getAnnotation(RemoteDestination.class); 
094        if (anno != null && !("".equals(anno.id()))) {
095            XMap props = new XMap("properties");
096
097            // Owning service.
098            Service service = null;
099            if (anno.service().length() > 0) {
100                log.info("Configuring service from RemoteDestination annotation: service=%s (class=%s, anno=%s)", anno.service(), clazz, anno);
101                service = this.services.get(anno.service());
102            }
103            else if (this.services.size() > 0) {
104                // Lookup remoting service
105                log.info("Looking for service(s) with RemotingMessage type (class=%s, anno=%s)", clazz, anno);
106                int count = 0;
107                for (Service s : this.services.values()) {
108                    if (RemotingMessage.class.getName().equals(s.getMessageTypes())) {
109                        log.info("Found service with RemotingMessage type: service=%s (class=%s, anno=%s)", s, clazz, anno);
110                        service = s;
111                        count++;
112                    }
113                }
114                if (count == 1 && service != null)
115                    log.info("Service " + service.getId() + " selected for destination in class: " + clazz.getName());
116                else {
117                        log.error("Found %d matching services (should be exactly 1, class=%s, anno=%s)", count, clazz, anno);
118                        service = null;
119                }
120            }
121            if (service == null)
122                throw new RuntimeException("No service found for destination in class: " + clazz.getName());
123            
124            // Channel reference.
125            List<String> channelIds = null;
126            if (anno.channels().length > 0)
127                channelIds = Arrays.asList(anno.channels());
128            else if (anno.channel().length() > 0)
129                channelIds = Collections.singletonList(anno.channel());
130            else if (this.channels.size() == 1) {
131                channelIds = new ArrayList<String>(this.channels.keySet());
132                log.info("Channel " + channelIds.get(0) + " selected for destination in class: " + clazz.getName());
133            }
134            else {
135                log.warn("No (or ambiguous) channel definition found for destination in class: " + clazz.getName());
136                channelIds = Collections.emptyList();
137            }
138            
139            // Factory reference.
140            String factoryId = null;
141            if (anno.factory().length() > 0)
142                factoryId = anno.factory();
143            else if (this.factories.isEmpty()) {
144                props.put("scope", anno.scope());
145                props.put("source", clazz.getName());
146                log.info("Default POJO factory selected for destination in class: " + clazz.getName() + " with scope: " + anno.scope());
147            }
148            else if (this.factories.size() == 1) {
149                factoryId = this.factories.keySet().iterator().next();
150                log.info("Factory " + factoryId + " selected for destination in class: " + clazz.getName());
151            }
152            else
153                throw new RuntimeException("No (or ambiguous) factory definition found for destination in class: " + clazz.getName());
154            
155            if (factoryId != null)
156                props.put("factory", factoryId);
157            if (!(anno.source().equals("")))
158                props.put("source", anno.source());
159            
160            // Security roles.
161            List<String> roles = null;
162            if (anno.securityRoles().length > 0) {
163                roles = new ArrayList<String>(anno.securityRoles().length);
164                for (String role : anno.securityRoles())
165                        roles.add(role);
166            }
167            
168            // Securizer
169            if (anno.securizer() != RemotingDestinationSecurizer.class)
170                props.put("securizer", anno.securizer().getName());
171            
172            Destination destination = new Destination(anno.id(), channelIds, props, roles, null, clazz);
173            
174            service.getDestinations().put(destination.getId(), destination);
175        }
176    }
177
178    ///////////////////////////////////////////////////////////////////////////
179    // Static ServicesConfig loaders.
180    
181    public ServicesConfig(InputStream customConfigIs, Configuration configuration, boolean scan) throws IOException, SAXException {
182        if (customConfigIs != null)
183                loadConfig(customConfigIs);
184        
185        if (scan)
186                scan(configuration);
187    }
188    
189    public void scan(Configuration configuration) {
190        List<ScannedItemHandler> handlers = new ArrayList<ScannedItemHandler>();
191        for (Factory factory : factories.values()) {
192                try {
193                        Class<?> clazz = TypeUtil.forName(factory.getClassName());
194                        Method method = clazz.getMethod("getScannedItemHandler");
195                        if ((Modifier.STATIC & method.getModifiers()) != 0 && method.getParameterTypes().length == 0) {
196                                ScannedItemHandler handler = (ScannedItemHandler)method.invoke(null);
197                                handlers.add(handler);
198                        }
199                        else
200                                log.warn("Factory class %s contains an unexpected signature for method: %s", factory.getClassName(), method);
201                }
202                catch (NoSuchMethodException e) {
203                        // ignore
204                }
205                catch (ClassNotFoundException e) {
206                        log.error(e, "Could not load factory class: %s", factory.getClassName());
207                }
208                catch (Exception e) {
209                        log.error(e, "Error while calling %s.getScannedItemHandler() method", factory.getClassName());
210                }
211        }
212        scanConfig(configuration != null ? configuration.getFlexServicesConfigProperties() : null, handlers);
213    }
214
215    private void loadConfig(InputStream configIs) throws IOException, SAXException {
216        XMap doc = new XMap(configIs);
217        forElement(doc);
218    }
219
220    ///////////////////////////////////////////////////////////////////////////
221    // Services.
222
223    public Service findServiceById(String id) {
224        return services.get(id);
225    }
226
227    public List<Service> findServicesByMessageType(String messageType) {
228        List<Service> services = new ArrayList<Service>();
229        for (Service service : this.services.values()) {
230            if (messageType.equals(service.getMessageTypes()))
231                services.add(service);
232        }
233        return services;
234    }
235
236    public void addService(Service service) {
237        services.put(service.getId(), service);
238    }
239
240    ///////////////////////////////////////////////////////////////////////////
241    // Channels.
242
243    public Channel findChannelById(String id) {
244        return channels.get(id);
245    }
246
247    public void addChannel(Channel channel) {
248        channels.put(channel.getId(), channel);
249    }
250
251    ///////////////////////////////////////////////////////////////////////////
252    // Factories.
253
254    public Factory findFactoryById(String id) {
255        return factories.get(id);
256    }
257
258    public void addFactory(Factory factory) {
259        factories.put(factory.getId(), factory);
260    }
261
262    ///////////////////////////////////////////////////////////////////////////
263    // Destinations.
264
265    public Destination findDestinationById(String messageType, String id) {
266        for (Service service : services.values()) {
267            if (messageType == null || messageType.equals(service.getMessageTypes())) {
268                Destination destination = service.findDestinationById(id);
269                if (destination != null)
270                    return destination;
271            }
272        }
273        return null;
274    }
275
276    public List<Destination> findDestinationsByMessageType(String messageType) {
277        List<Destination> destinations = new ArrayList<Destination>();
278        for (Service service : services.values()) {
279            if (messageType.equals(service.getMessageTypes()))
280                destinations.addAll(service.getDestinations().values());
281        }
282        return destinations;
283    }
284
285    ///////////////////////////////////////////////////////////////////////////
286    // Static helper.
287
288    private void forElement(XMap element) {
289        XMap services = element.getOne("services");
290        if (services != null) {
291            for (XMap service : services.getAll("service")) {
292                Service serv = Service.forElement(service);
293                this.services.put(serv.getId(), serv);
294            }
295
296            /* TODO: service-include...
297            for (Element service : (List<Element>)services.getChildren("service-include")) {
298                config.services.add(Service.forElement(service));
299            }
300            */
301        }
302
303        XMap channels = element.getOne("channels");
304        if (channels != null) {
305            for (XMap channel : channels.getAll("channel-definition")) {
306                Channel chan = Channel.forElement(channel);
307                this.channels.put(chan.getId(), chan);
308            }
309        }
310        else {
311            log.info("No channel definition found, using defaults");
312            EndPoint defaultEndpoint = new EndPoint("http://{server.name}:{server.port}/{context.root}/graniteamf/amf", "flex.messaging.endpoints.AMFEndpoint");
313            Channel defaultChannel = new Channel("my-graniteamf", "mx.messaging.channels.AMFChannel", defaultEndpoint, XMap.EMPTY_XMAP);
314            this.channels.put(defaultChannel.getId(), defaultChannel);
315        }
316
317        XMap factories = element.getOne("factories");
318        if (factories != null) {
319            for (XMap factory : factories.getAll("factory")) {
320                Factory fact = Factory.forElement(factory);
321                this.factories.put(fact.getId(), fact);
322            }
323        }
324    }
325    
326    
327    /**
328     * Remove service (new addings for osgi).
329     * @param clazz service class.
330     */
331    public void handleRemoveService(Class<?> clazz){
332         RemoteDestination anno = clazz.getAnnotation(RemoteDestination.class);
333         if(anno!=null){
334                 Service service=null;
335                 if (anno.service().length() > 0){
336                          service=services.get(anno.service());
337                 }
338                 else if (services.size() > 0) {
339                // Lookup remoting service
340                for (Service s :  services.values()) {
341                    if (RemotingMessage.class.getName().equals(s.getMessageTypes())) {
342                        service = s;
343                        log.info("Service " + service.getId() + " selected for destination in class: " + clazz.getName());
344                        break;
345                    }
346                }
347            }
348                if(service!=null){
349                        Destination dest=service.getDestinations().remove(anno.id());
350                        if (dest != null) {
351                                dest.remove();
352                                log.info("RemoteDestination:"+dest.getId()+" has been removed");
353                        }
354                }else{
355                        log.info("Service NOT Found!!");
356                }
357         }
358    }
359    
360}