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