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     */
022    package org.granite.config.flex;
023    
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.lang.reflect.Method;
027    import java.lang.reflect.Modifier;
028    import java.util.ArrayList;
029    import java.util.Arrays;
030    import java.util.Collections;
031    import java.util.HashMap;
032    import java.util.List;
033    import java.util.Map;
034    
035    import org.granite.config.api.Configuration;
036    import org.granite.logging.Logger;
037    import org.granite.messaging.service.annotations.RemoteDestination;
038    import org.granite.messaging.service.security.RemotingDestinationSecurizer;
039    import org.granite.scan.ScannedItem;
040    import org.granite.scan.ScannedItemHandler;
041    import org.granite.scan.Scanner;
042    import org.granite.scan.ScannerFactory;
043    import org.granite.util.TypeUtil;
044    import org.granite.util.XMap;
045    import org.xml.sax.SAXException;
046    
047    import flex.messaging.messages.RemotingMessage;
048    
049    
050    /**
051     * @author Franck WOLFF
052     */
053    public 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    }