Author: ngn
Date: Mon May 24 20:25:45 2010
New Revision: 947790

URL: http://svn.apache.org/viewvc?rev=947790&view=rev
Log:
Adding module for BOSH

Added:
    mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/
    mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/pom.xml
    mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/
    mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/
    mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/
    mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/
    
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/
    
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/
    
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/
    
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/
    
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/
    
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContext.java
    
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshEndpoint.java
    
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshIoHandler.java
    
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/ServerMain.java

Added: mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/pom.xml
URL: 
http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/pom.xml?rev=947790&view=auto
==============================================================================
--- mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/pom.xml (added)
+++ mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/pom.xml Mon May 24 
20:25:45 2010
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+  <!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements. See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to you under the Apache License, Version
+    2.0 (the "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0 
+    
+    Unless required by applicable law or agreed to in writing, software 
+    distributed under the License is distributed on an "AS IS" BASIS, 
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+    See the License for the specific language governing permissions and 
+    limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+  <parent>
+    <artifactId>vysper-extensions</artifactId>
+    <groupId>org.apache.vysper</groupId>
+    <version>0.6-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.vysper.extensions</groupId>
+  <artifactId>xep0124-xep0206-bosh</artifactId>
+  <name>Apache Vysper XEP-0124 and XEP-0206 BOSH</name>
+  <version>0.6-SNAPSHOT</version>
+  
+  
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.vysper</groupId>
+      <artifactId>spec-compliance</artifactId>
+      <optional>true</optional>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.vysper</groupId>
+      <artifactId>vysper-core</artifactId>
+    </dependency>
+    
+       <dependency>
+         <groupId>org.eclipse.jetty</groupId>
+         <artifactId>jetty-server</artifactId>
+         <version>7.1.1.v20100517</version>
+       </dependency>
+
+    <!-- Runtime dependencies -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+      
+    <!-- Test dependencies -->
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>

Added: 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContext.java
URL: 
http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContext.java?rev=947790&view=auto
==============================================================================
--- 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContext.java
 (added)
+++ 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContext.java
 Mon May 24 20:25:45 2010
@@ -0,0 +1,89 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.vysper.xmpp.extension.xep0124;
+
+import org.apache.mina.core.future.CloseFuture;
+import org.apache.mina.core.future.IoFuture;
+import org.apache.mina.core.future.IoFutureListener;
+import org.apache.mina.core.session.IoSession;
+import org.apache.vysper.mina.codec.StanzaWriteInfo;
+import org.apache.vysper.xmpp.protocol.SessionStateHolder;
+import org.apache.vysper.xmpp.server.AbstractSessionContext;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionState;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.writer.StanzaWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * connects MINA 2 frontend to the vysper backend through the Bosh protocol
+ *
+ * @author The Apache MINA Project ([email protected])
+ */
+public class BoshBackedSessionContext extends AbstractSessionContext 
implements StanzaWriter, IoFutureListener {
+
+    final Logger logger = 
LoggerFactory.getLogger(BoshBackedSessionContext.class);
+
+    private IoSession minaSession;
+    private boolean openingStanzaWritten = false;
+    protected CloseFuture closeFuture;
+
+    public BoshBackedSessionContext(
+            ServerRuntimeContext serverRuntimeContext,
+            SessionStateHolder sessionStateHolder,
+            IoSession minaSession) {
+        super(serverRuntimeContext, sessionStateHolder);
+        this.minaSession = minaSession;
+        closeFuture = minaSession.getCloseFuture();
+        closeFuture.addListener(this);
+        sessionStateHolder.setState(SessionState.INITIATED); // connection 
established
+    }
+
+    public StanzaWriter getResponseWriter() {
+        return this;
+    }
+
+    public void setIsReopeningXMLStream() {
+        openingStanzaWritten = false;
+    }
+
+    public void write(Stanza stanza) {
+        minaSession.write(new StanzaWriteInfo(stanza, !openingStanzaWritten));
+        openingStanzaWritten = true;
+    }
+
+    public void close() {
+        logger.info("session will be closed now");
+        closeFuture.setClosed();
+        minaSession.close(true);
+    }
+
+    public void operationComplete(IoFuture ioFuture) {
+        // close future notification
+        logger.info("close future called");
+    }
+
+       public void switchToTLS() {
+               // Bosh cannot switch dynamically
+               // SSL can be enabled/disabled in BoshEndpoint#setSslEnabled()
+       }
+}

Added: 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshEndpoint.java
URL: 
http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshEndpoint.java?rev=947790&view=auto
==============================================================================
--- 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshEndpoint.java
 (added)
+++ 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshEndpoint.java
 Mon May 24 20:25:45 2010
@@ -0,0 +1,77 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.vysper.xmpp.extension.xep0124;
+
+import java.io.IOException;
+
+import org.apache.vysper.xmpp.server.Endpoint;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.eclipse.jetty.server.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Allows HTTP clients to communicate through the Bosh protocol 
(http://xmpp.org/extensions/xep-0124.html)
+ *
+ * @author The Apache MINA Project ([email protected])
+ */
+public class BoshEndpoint implements Endpoint {
+       
+       private final Logger logger = 
LoggerFactory.getLogger(BoshEndpoint.class);
+
+    private ServerRuntimeContext serverRuntimeContext;
+
+    private int port = 8080;
+
+    private Server server;
+    
+    public void setServerRuntimeContext(ServerRuntimeContext 
serverRuntimeContext) {
+        this.serverRuntimeContext = serverRuntimeContext;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+    
+    public void setSslEnabled(boolean value) {
+       // TODO:
+    }
+    
+    public void start() throws IOException {
+       server = new Server(port);
+       BoshIoHandler boshIoHandler = new BoshIoHandler();
+       boshIoHandler.setServerRuntimeContext(serverRuntimeContext);
+       server.setHandler(boshIoHandler); 
+       try {
+                       server.start();
+               } catch (Exception e) {
+                       throw new IOException(e);
+               }
+    }
+
+    public void stop() {
+       try {
+                       server.stop();
+               } catch (Exception e) {
+                       logger.warn("Could not stop the Jetty server", e);
+               }
+    }
+
+}

Added: 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshIoHandler.java
URL: 
http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshIoHandler.java?rev=947790&view=auto
==============================================================================
--- 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshIoHandler.java
 (added)
+++ 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshIoHandler.java
 Mon May 24 20:25:45 2010
@@ -0,0 +1,110 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.vysper.xmpp.extension.xep0124;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.Random;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handles Bosh stanzas
+ *
+ * @author The Apache MINA Project ([email protected])
+ */
+public class BoshIoHandler extends AbstractHandler {
+
+    final Logger logger = LoggerFactory.getLogger(BoshIoHandler.class);
+
+    private ServerRuntimeContext serverRuntimeContext;
+
+    public void setServerRuntimeContext(ServerRuntimeContext 
serverRuntimeContext) {
+        this.serverRuntimeContext = serverRuntimeContext;
+    }
+    
+    public void handle(String target, Request baseRequest, 
+                       HttpServletRequest request, HttpServletResponse 
response)
+                       throws IOException, ServletException {
+       
+       if (request.getMethod().equals("GET") && 
request.getPathInfo().equals("/crossdomain.xml")) {
+               // request for the cross-domain policy from Flash (e.g. flXHR)
+               response.setContentType("text/xml");
+               response.setStatus(HttpServletResponse.SC_OK);
+               
response.getWriter().println("<cross-domain-policy><site-control 
permitted-cross-domain-policies='all'/><allow-access-from 
domain='*'/><allow-http-request-headers-from domain='*' 
headers='*'/></cross-domain-policy>");
+               ((Request)request).setHandled(true);
+               return;
+       }
+       
+       if (!request.getMethod().equals("POST")) {
+               // it should not reach here normally, but it can happen if the 
user explicitly makes a non-POST request (e.g. GET, DELETE, etc)
+               response.setContentType("text/html");
+               response.setStatus(HttpServletResponse.SC_OK);
+               response.getWriter().println("<html>This is an XMPP BOSH 
connection manager, you need to use a compatible BOSH client to use its 
services!</html>");
+               ((Request)request).setHandled(true);
+               return;
+       }
+       
+       BufferedReader reader = request.getReader();
+       
+       char [] buf = new char[1024];
+       StringBuilder sb = new StringBuilder();
+       
+       for (;;) {
+               int n = reader.read(buf);
+               if (n == -1) {
+                       break;
+               }
+               sb.append(buf, 0, n);
+       }
+       
+       String body = sb.toString();
+       logger.debug("BOSH CM received : {}", body);
+       
+       
+       // test if this is the first request (kind of a hack - should be 
parsing XML)
+       if (body.indexOf("sid=") == -1) {
+               // initial request
+               String sid = Long.toString(new Random().nextLong(), 16);
+               response.setContentType("text/xml; charset=utf-8");
+               response.setStatus(HttpServletResponse.SC_OK);
+               response.getWriter().print("<body 
xmlns='http://jabber.org/protocol/httpbind' wait='60' inactivity='60' 
polling='5' requests='2' hold='1' maxpause='120' sid='");
+               response.getWriter().print(sid);
+               response.getWriter().print("' ver='1.6' from='vysper.org'/>");
+               ((Request)request).setHandled(true);
+               return;
+       }
+       
+       // session exists
+       // not handled yet, TODO
+       response.setContentType("text/xml; charset=utf-8");
+               response.setStatus(HttpServletResponse.SC_OK);
+       ((Request)request).setHandled(true);
+    }
+
+}

Added: 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/ServerMain.java
URL: 
http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/ServerMain.java?rev=947790&view=auto
==============================================================================
--- 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/ServerMain.java
 (added)
+++ 
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/ServerMain.java
 Mon May 24 20:25:45 2010
@@ -0,0 +1,136 @@
+package org.apache.vysper.xmpp.extension.xep0124;
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.vysper.mina.TCPEndpoint;
+import org.apache.vysper.storage.StorageProviderRegistry;
+import org.apache.vysper.storage.inmemory.MemoryStorageProviderRegistry;
+import org.apache.vysper.xmpp.addressing.EntityFormatException;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.authorization.AccountCreationException;
+import org.apache.vysper.xmpp.authorization.AccountManagement;
+import org.apache.vysper.xmpp.modules.Module;
+import 
org.apache.vysper.xmpp.modules.extension.xep0049_privatedata.PrivateDataModule;
+import 
org.apache.vysper.xmpp.modules.extension.xep0054_vcardtemp.VcardTempModule;
+import 
org.apache.vysper.xmpp.modules.extension.xep0092_software_version.SoftwareVersionModule;
+import 
org.apache.vysper.xmpp.modules.extension.xep0119_xmppping.XmppPingModule;
+import 
org.apache.vysper.xmpp.modules.extension.xep0202_entity_time.EntityTimeModule;
+import org.apache.vysper.xmpp.server.XMPPServer;
+
+/**
+ * starts the server as a standalone application
+ *
+ * @author The Apache MINA Project ([email protected])
+ */
+public class ServerMain {
+
+    /**
+     * boots the server as a standalone application
+     * 
+     * adding a module from the command line:
+     * using a runtime property, one or more modules can be specified, like 
this:
+     * 
-Dvysper.add.module=org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.PublishSubscribeModule,...
 more ...
+     * 
+     * @param args
+     */
+    public static void main(String[] args) throws AccountCreationException, 
EntityFormatException {
+
+        String addedModuleProperty = System.getProperty("vysper.add.module");
+        List<Module> listOfModules = null;
+        if (addedModuleProperty != null) {
+            String[] moduleClassNames = addedModuleProperty.split(",");
+            listOfModules = createModuleInstances(moduleClassNames);
+        }
+
+        // choose the storage you want to use
+        //StorageProviderRegistry providerRegistry = new 
JcrStorageProviderRegistry();
+        StorageProviderRegistry providerRegistry = new 
MemoryStorageProviderRegistry();
+
+        final AccountManagement accountManagement = 
(AccountManagement)providerRegistry.retrieve(AccountManagement.class);
+
+        if 
(!accountManagement.verifyAccountExists(EntityImpl.parse("[email protected]"))) {
+            accountManagement.addUser("[email protected]", "password1");
+        }
+        if 
(!accountManagement.verifyAccountExists(EntityImpl.parse("[email protected]"))) {
+            accountManagement.addUser("[email protected]", "password1");
+        }
+        if 
(!accountManagement.verifyAccountExists(EntityImpl.parse("[email protected]"))) {
+            accountManagement.addUser("[email protected]", "password1");
+        }
+
+        XMPPServer server = new XMPPServer("vysper.org");
+        server.addEndpoint(new TCPEndpoint());
+        BoshEndpoint boshEndpoint = new BoshEndpoint();
+        server.addEndpoint(boshEndpoint);
+        //server.addEndpoint(new StanzaSessionFactory());
+        server.setStorageProviderRegistry(providerRegistry);
+
+        server.setTLSCertificateInfo(new 
File("src/main/config/bogus_mina_tls.cert"), "boguspw");
+
+        try {
+            server.start();
+            System.out.println("vysper server is running...");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        server.addModule(new SoftwareVersionModule());
+        server.addModule(new EntityTimeModule());
+        server.addModule(new VcardTempModule());
+        server.addModule(new XmppPingModule());
+        server.addModule(new PrivateDataModule());
+
+        if (listOfModules != null) {
+            for (Module module : listOfModules) {
+                server.addModule(module);
+            }
+        }
+    }
+
+    private static List<Module> createModuleInstances(String[] 
moduleClassNames) {
+        List<Module> modules = new ArrayList<Module>();
+        
+        for (String moduleClassName : moduleClassNames) {
+            Class<Module> moduleClass;
+            try {
+                moduleClass = (Class<Module>)Class.forName(moduleClassName);
+            } catch (ClassCastException e) {
+                System.err.println("not a Vysper module class: " + 
moduleClassName);
+                continue;
+            } catch (ClassNotFoundException e) {
+                System.err.println("could not load module class " + 
moduleClassName);
+                continue;
+            }
+            try {
+                Module module = moduleClass.newInstance();
+                modules.add(module);
+            } catch (Exception e) {
+                System.err.println("failed to instantiate module class " + 
moduleClassName);
+                continue;
+            }
+        }
+        return modules;
+    }
+}


Reply via email to