geronimo-scm mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hanhongf...@apache.org
Subject svn commit: r1080130 - /geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/
Date Thu, 10 Mar 2011 07:22:28 GMT
Author: hanhongfang
Date: Thu Mar 10 07:22:28 2011
New Revision: 1080130

URL: http://svn.apache.org/viewvc?rev=1080130&view=rev
Log:
GERONIMODEVTOOLS-707 force Eclipse plugin to refresh and realize the application was undeployed
if an application is undeployed through the admin console or Karaf shell

Added:
    geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/SynchronizeProjectOnServerTask.java
Modified:
    geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/Activator.java
    geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/GeronimoServerBehaviourDelegate.java
    geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/ModuleArtifactMapper.java

Modified: geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/Activator.java
URL: http://svn.apache.org/viewvc/geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/Activator.java?rev=1080130&r1=1080129&r2=1080130&view=diff
==============================================================================
--- geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/Activator.java
(original)
+++ geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/Activator.java
Thu Mar 10 07:22:28 2011
@@ -20,7 +20,9 @@ import org.eclipse.core.runtime.Plugin;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.wst.server.core.IServer;
 import org.eclipse.wst.server.core.IServerLifecycleListener;
+import org.eclipse.wst.server.core.IServerListener;
 import org.eclipse.wst.server.core.ServerCore;
+import org.eclipse.wst.server.core.ServerEvent;
 import org.osgi.framework.BundleContext;
 
 /**
@@ -53,6 +55,7 @@ public class Activator extends Plugin {
         ServerCore.addServerLifecycleListener(new IServerLifecycleListener() {
             public void serverAdded(IServer server) {
                 triggerStartUpdateServerTask(server);
+                triggerSynchronizeProjectOnServerTask(server);
             }
 
             public void serverChanged(IServer server) {
@@ -65,9 +68,48 @@ public class Activator extends Plugin {
         IServer[] servers = ServerCore.getServers();
         for(int i = 0; i < servers.length; i++) {
             triggerStartUpdateServerTask(servers[i]);
+            triggerSynchronizeProjectOnServerTask(servers[i]);
         }
     }
 
+    /**
+     * 
+     * @param server
+     */
+    protected void triggerSynchronizeProjectOnServerTask(IServer server) {
+      
+        IServerListener listener = new IServerListener() {
+            public void serverChanged(ServerEvent event) {
+                int eventKind = event.getKind();
+                if ((eventKind & ServerEvent.STATE_CHANGE) != 0) {
+                    int state = event.getServer().getServerState();
+                    if (state == IServer.STATE_STARTED) {
+                        GeronimoServerBehaviourDelegate delegate = getGeronimoServerBehaviourDelegate(event.getServer());
+                        if (delegate != null) {
+                            delegate.startSynchronizeProjectOnServerTask();
+                        }                        
+                    } else if (state == IServer.STATE_STOPPED) {
+                        GeronimoServerBehaviourDelegate delegate = getGeronimoServerBehaviourDelegate(event.getServer());
+                        if (delegate != null) {
+                            delegate.stopSynchronizeProjectOnServerTask();
+                        }                        
+                    }
+                }
+            }
+        }; 
+        
+        server.addServerListener(listener);
+    }
+    
+    final protected GeronimoServerBehaviourDelegate getGeronimoServerBehaviourDelegate (IServer
server) {
+        
+        GeronimoServerBehaviourDelegate delegate = (GeronimoServerBehaviourDelegate) server.getAdapter(GeronimoServerBehaviourDelegate.class);
+        if (delegate == null) {
+            delegate = (GeronimoServerBehaviourDelegate) server.loadAdapter(GeronimoServerBehaviourDelegate.class,
null);
+        }
+        return delegate;
+    }
+    
     /** 
      * <b>triggerStartUpdateServerTask</b> is invoked from:
      * <ul> 

Modified: geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/GeronimoServerBehaviourDelegate.java
URL: http://svn.apache.org/viewvc/geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/GeronimoServerBehaviourDelegate.java?rev=1080130&r1=1080129&r2=1080130&view=diff
==============================================================================
--- geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/GeronimoServerBehaviourDelegate.java
(original)
+++ geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/GeronimoServerBehaviourDelegate.java
Thu Mar 10 07:22:28 2011
@@ -40,6 +40,7 @@ import javax.management.remote.JMXConnec
 import javax.management.remote.JMXConnectorFactory;
 import javax.management.remote.JMXServiceURL;
 
+import org.apache.geronimo.st.v30.core.UpdateServerStateTask;
 import org.apache.geronimo.st.v30.core.commands.DeploymentCmdStatus;
 import org.apache.geronimo.st.v30.core.commands.DeploymentCommandFactory;
 import org.apache.geronimo.st.v30.core.commands.IDeploymentCommand;
@@ -94,7 +95,9 @@ abstract public class GeronimoServerBeha
 
     protected IProgressMonitor _monitor;
 
-    protected Timer timer = null;
+    protected Timer stateTimer = null;
+    
+    protected Timer synchronizerTimer = null;
 
     protected PingThread pingThread;
 
@@ -394,6 +397,7 @@ abstract public class GeronimoServerBeha
      */
     public void dispose() {
         stopUpdateServerStateTask();
+        stopSynchronizeProjectOnServerTask();
     }
 
     public abstract String getRuntimeClass();
@@ -494,13 +498,29 @@ abstract public class GeronimoServerBeha
         Trace.tracePoint("Entry", "GeronimoServerBehaviourDelegate.doAdded", module.getName(),
configId);
         
         configId = getLastKnowConfigurationId(module, configId);
+        
+        IStatus status;
+        TargetModuleID[] ids;
+        
         if (configId == null) {
-            IStatus status = distribute(module);
-            if (!status.isOK()) {
-                doFail(status, Messages.DISTRIBUTE_FAIL);
-            }
+            HashMap artifactsMap = ModuleArtifactMapper.getInstance().getServerArtifactsMap(getServer());
+            if (artifactsMap != null) {
+                synchronized (artifactsMap) {
+                    status = distribute(module);
+                    if (!status.isOK()) {
+                        doFail(status, Messages.DISTRIBUTE_FAIL);
+                    }
+
+                    ids = updateServerModuleConfigIDMap(module, status);
+                } 
+            } else {             
+                status = distribute(module);
+                if (!status.isOK()) {
+                    doFail(status, Messages.DISTRIBUTE_FAIL);
+                }
 
-            TargetModuleID[] ids = updateServerModuleConfigIDMap(module, status);
+                ids = updateServerModuleConfigIDMap(module, status);
+            }
 
             status = start(ids);
             if (!status.isOK()) {
@@ -515,7 +535,6 @@ abstract public class GeronimoServerBeha
 
         Trace.tracePoint("Exit ", "GeronimoServerBehaviourDelegate.doAdded");
     }
-
     /**
      * @param module
      * @param configId the forced configId to process this method, passed in when invoked
from doAdded()
@@ -665,14 +684,25 @@ abstract public class GeronimoServerBeha
     protected void doRemoved(IModule module) throws Exception {
         Trace.tracePoint("Entry", "GeronimoServerBehaviourDelegate.doRemoved", module.getName());
 
+        HashMap artifactsMap = ModuleArtifactMapper.getInstance().getServerArtifactsMap(getServer());
+        if (artifactsMap != null) {
+            synchronized (artifactsMap) {
+                _doRemove(module);
+            }    
+        } else {
+            _doRemove(module);
+        }
+
+        Trace.tracePoint("Exit ", "GeronimoServerBehaviourDelegate.doRemoved");
+    }
+    
+    private void _doRemove(IModule module) throws Exception {
         IStatus status = unDeploy(module);
         if (!status.isOK()) {
             doFail(status, Messages.UNDEPLOY_FAIL);
         }
-        
-        ModuleArtifactMapper.getInstance().removeEntry(getServer(), module.getProject());
 
-        Trace.tracePoint("Exit ", "GeronimoServerBehaviourDelegate.doRemoved");
+        ModuleArtifactMapper.getInstance().removeEntry(getServer(), module.getProject());
     }
     
     protected void doNoChange(IModule module) throws Exception {
@@ -887,20 +917,42 @@ abstract public class GeronimoServerBeha
     public void startUpdateServerStateTask() {
         Trace.tracePoint("Entry", "GeronimoServerBehaviourDelegate.startUpdateServerStateTask",
getServer().getName());
 
-        timer = new Timer(true);
-        timer.schedule(new UpdateServerStateTask(this, getServer()), TIMER_TASK_DELAY * 1000,
TIMER_TASK_INTERVAL * 1000);
+        stateTimer = new Timer(true);
+        stateTimer.schedule(new UpdateServerStateTask(this, getServer()), TIMER_TASK_DELAY
* 1000, TIMER_TASK_INTERVAL * 1000);
 
         Trace.tracePoint("Exit ", "GeronimoServerBehaviourDelegate.startUpdateServerStateTask");
     }
+    
+    public void startSynchronizeProjectOnServerTask() {
+        Trace.tracePoint("Entry", "GeronimoServerBehaviourDelegate.startSynchronizeProjectOnServerTask",
getServer().getName());
+
+        if (synchronizerTimer != null) {
+            synchronizerTimer.cancel();
+        }
+        
+        synchronizerTimer = new Timer(true);
+        synchronizerTimer.schedule(new SynchronizeProjectOnServerTask(this, getServer()),
TIMER_TASK_DELAY * 1000, TIMER_TASK_INTERVAL * 1000);
+        
+        Trace.tracePoint("Exit ", "GeronimoServerBehaviourDelegate.startSynchronizeProjectOnServerTask");
+    }
 
     public void stopUpdateServerStateTask() {
         Trace.tracePoint("Entry", "GeronimoServerBehaviourDelegate.stopUpdateServerStateTask");
 
-        if (timer != null)
-            timer.cancel();
+        if (stateTimer != null)
+            stateTimer.cancel();
 
         Trace.tracePoint("Exit ", "GeronimoServerBehaviourDelegate.stopUpdateServerStateTask");
     }
+    
+    public void stopSynchronizeProjectOnServerTask() {
+        Trace.tracePoint("Entry", "GeronimoServerBehaviourDelegate.stopSynchronizeProjectOnServerTask");
+        
+        if (synchronizerTimer != null)
+            synchronizerTimer.cancel(); 
+
+        Trace.tracePoint("Exit ", "GeronimoServerBehaviourDelegate.stopSynchronizeProjectOnServerTask");
+    }
 
     protected IPath getModulePath(IModule[] module, URL baseURL) {
         Trace.tracePoint("Entry", "GeronimoServerBehaviourDelegate.getModulePath", Arrays.asList(module).toString(),
baseURL);

Modified: geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/ModuleArtifactMapper.java
URL: http://svn.apache.org/viewvc/geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/ModuleArtifactMapper.java?rev=1080130&r1=1080129&r2=1080130&view=diff
==============================================================================
--- geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/ModuleArtifactMapper.java
(original)
+++ geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/ModuleArtifactMapper.java
Thu Mar 10 07:22:28 2011
@@ -58,7 +58,7 @@ public class ModuleArtifactMapper {
         return instance;
     }
 
-    public void addEntry(IServer server, IProject project, String configId) {
+    synchronized public void addEntry(IServer server, IProject project, String configId)
{
 
         if (!SocketUtil.isLocalhost(server.getHost()))
             return;
@@ -73,7 +73,7 @@ public class ModuleArtifactMapper {
         artifactEntries.put(project.getName(), configId);
     }
 
-    public void removeEntry(IServer server, IProject project) {
+    synchronized public void removeEntry(IServer server, IProject project) {
 
         if (!SocketUtil.isLocalhost(server.getHost()))
             return;
@@ -85,7 +85,7 @@ public class ModuleArtifactMapper {
         }
     }
 
-    public String resolve(IServer server, IModule module) {
+    synchronized public String resolve(IServer server, IModule module) {
         Map artifactEntries = (Map) serverEntries.get(server.getRuntime().getLocation().toFile());
         if (artifactEntries != null && module != null && module.getProject()
!= null) {
             return (String) artifactEntries.get(module.getProject().getName());
@@ -93,7 +93,7 @@ public class ModuleArtifactMapper {
         return null;
     }
 
-    public void save() {
+    synchronized public void save() {
         ObjectOutput output = null;
         try {
             IPath dest = Activator.getDefault().getStateLocation().append(FILE_NAME);
@@ -114,7 +114,7 @@ public class ModuleArtifactMapper {
         }
     }
 
-    private void load() {
+    synchronized private void load() {
         ObjectInput input = null;
         try {
             IPath dest = Activator.getDefault().getStateLocation().append(FILE_NAME);
@@ -213,4 +213,19 @@ public class ModuleArtifactMapper {
             return xmlString;
         }
     }
+    
+    synchronized public HashMap getServerArtifactsMap(IServer server) {
+        if (!SocketUtil.isLocalhost(server.getHost())) {
+            return null;
+        }            
+        
+        File runtimeLoc = server.getRuntime().getLocation().toFile();  
+        HashMap artifactEntries = (HashMap) serverEntries.get(runtimeLoc);
+        if (artifactEntries == null) {
+            artifactEntries = new HashMap();
+            serverEntries.put(runtimeLoc, artifactEntries);
+        }
+        
+        return artifactEntries;        
+    }
 }

Added: geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/SynchronizeProjectOnServerTask.java
URL: http://svn.apache.org/viewvc/geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/SynchronizeProjectOnServerTask.java?rev=1080130&view=auto
==============================================================================
--- geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/SynchronizeProjectOnServerTask.java
(added)
+++ geronimo/devtools/eclipse-plugin/trunk/plugins/org.apache.geronimo.st.v30.core/src/main/java/org/apache/geronimo/st/v30/core/SynchronizeProjectOnServerTask.java
Thu Mar 10 07:22:28 2011
@@ -0,0 +1,207 @@
+/*
+ * 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.
+ */
+
+/**
+ * @version $Rev: 817996 $ $Date: 2009-09-23 16:04:12 +0800 (Wed, 23 Sep 2009) $
+ */
+package org.apache.geronimo.st.v30.core;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimerTask;
+import java.util.TreeSet;
+
+import javax.enterprise.deploy.spi.DeploymentManager;
+import javax.enterprise.deploy.spi.TargetModuleID;
+
+import org.apache.geronimo.st.v30.core.commands.DeploymentCommandFactory;
+import org.apache.geronimo.st.v30.core.internal.Trace;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.wst.server.core.IModule;
+import org.eclipse.wst.server.core.IServer;
+import org.eclipse.wst.server.core.IServerWorkingCopy;
+import org.eclipse.wst.server.core.ServerCore;
+import org.eclipse.wst.server.core.internal.ModuleFactory;
+import org.eclipse.wst.server.core.internal.ServerPlugin;
+
+public class SynchronizeProjectOnServerTask extends TimerTask {
+
+    private IGeronimoServerBehavior delegate;
+
+    private IServer server;
+
+    public SynchronizeProjectOnServerTask(IGeronimoServerBehavior delegate, IServer server)
{
+        super();
+        this.delegate = delegate;
+        this.server = server;
+    }
+
+    @Override
+    public void run() {
+
+        Trace.tracePoint("Entry ", "SynchronizeProjectOnServerTask.run");
+        
+        if (canUpdateState()) {            
+           
+            try {
+                HashMap projectsOnServer = ModuleArtifactMapper.getInstance().getServerArtifactsMap(server);
+                
+                if(projectsOnServer!=null && projectsOnServer.size() != 0) {
+                    
+                    synchronized (projectsOnServer) {                        
+                        Iterator projectsIterator = projectsOnServer.keySet().iterator();
+                        TreeSet<String> removedConfigIds = new TreeSet<String>();
+                        List<IProject> removedProjects = new ArrayList<IProject>();
+                        List<IModule> removedModules = new ArrayList<IModule>();
+                        
+                        DeploymentManager dm = DeploymentCommandFactory.getDeploymentManager(server);
+                        TargetModuleID[] ids = dm.getAvailableModules(null, dm.getTargets());
+                        
+                        for ( ; projectsIterator.hasNext(); ) {
+                            String projectName = (String) projectsIterator.next();
+                            String configID = (String) projectsOnServer.get(projectName);
+                            if (!isInstalledModule(ids, configID)) {
+                                removedConfigIds.add(configID);
+                                IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
+                                removedProjects.add(project);
+                                IModule[] modules = getModules(project);
+                                for (IModule module : modules) {
+                                    removedModules.add(module);
+                                }
+                            }
+                        }
+                        
+                        if (removedConfigIds.size() != 0 && removedModules.size()
!= 0) {
+                            IModule[] removedModules2 = new IModule[removedModules.size()];
+                            removedModules.toArray(removedModules2);
+                            removeModules(removedModules2, removedProjects);
+                        } else {
+                            Trace.trace(Trace.INFO, "SynchronizeProjectOnServerTask: no configuration
is removed outside eclipse on server: " + this.server.getId());
+                        }
+                        
+                    }                    
+                    
+                } else {
+                    Trace.trace(Trace.INFO, "SynchronizeProjectOnServerTask: no project has
been deployed on server: " + this.server.getId());
+                }                
+                
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        
+        Trace.tracePoint("Exist ", "SynchronizeProjectOnServerTask.run");
+    }
+
+    private boolean isInstalledModule(TargetModuleID[] ids, String configId) {
+        
+        if(ids == null) {           
+            return false;
+        }
+        
+        if (ids != null) {
+            for (int i = 0; i < ids.length; i++) {
+                if (ids[i].getModuleID().equals(configId)) {                    
+                    return true;
+                }
+            }
+        }
+            
+        return false;
+    }
+      
+    private void removeModules(IModule[] remove, List<IProject> projects) {
+        
+        Trace.tracePoint("Entry ", "SynchronizeProjectOnServerTask.removeModules", remove,
projects);
+        
+        IServerWorkingCopy wc = server.createWorkingCopy();
+        IProgressMonitor monitor = new NullProgressMonitor(); 
+        
+        try {
+            wc.modifyModules(null, remove, monitor);
+            server = wc.save(true, monitor); 
+
+            if (projects != null) {
+                for (IProject project : projects) {
+                    ModuleArtifactMapper.getInstance().removeEntry(this.server, project);
+                }
+            }           
+        } catch (CoreException e) {
+            Trace.trace(Trace.WARNING, "Could not remove module in SynchronizeProjectOnServerTask",
e);
+        }
+        
+        Trace.tracePoint("Exist ", "SynchronizeProjectOnServerTask.removeModules");
+    }
+
+    private IModule[] getModules(IProject project) {
+        ModuleFactory[] factories = ServerPlugin.getModuleFactories();
+        if (factories != null) {
+            for (ModuleFactory factory : factories) {
+                IModule[] modules = factory.getModules(project, new NullProgressMonitor());
+                if (modules != null && modules.length != 0) {
+                    return modules;
+                }
+            }
+        }
+        return new IModule [0];
+    }
+    
+    private boolean canUpdateState() {
+        
+        Trace.tracePoint("Entry ", "SynchronizeProjectOnServerTask.canUpdateState");
+        
+        boolean flag = true;
+        
+        if (server.getServerState()!=IServer.STATE_STARTED) {
+            flag = false;            
+        }
+
+        IGeronimoServer thisServer = (IGeronimoServer) this.server.loadAdapter(IGeronimoServer.class,
null);
+        IServer[] allServers = ServerCore.getServers();
+        for (int i = 0; i < allServers.length; i++) {
+            IServer server = allServers[i];
+            IGeronimoServer gs = (IGeronimoServer) server.loadAdapter(IGeronimoServer.class,
null);
+            if (gs != null && !this.server.getId().equals(server.getId())) {
+                if (isSameConnectionURL(gs, thisServer)) {
+                    if (!isSameRuntimeLocation(server) && server.getServerState()
!= IServer.STATE_STOPPED) {
+                        Trace.trace(Trace.WARNING, server.getId()
+                                + " Cannot update server state.  URL conflict between multiple
servers.");
+                        flag = false;
+                    }
+                }
+            }
+        }
+        
+        Trace.tracePoint("Exist ", "SynchronizeProjectOnServerTask.canUpdateState", flag);
     
+        return flag;
+    }
+
+    private boolean isSameRuntimeLocation(IServer server) {
+        return server.getRuntime().getLocation().equals(this.server.getRuntime().getLocation());
+    }
+
+    private boolean isSameConnectionURL(IGeronimoServer server, IGeronimoServer thisServer)
{
+        return server.getJMXServiceURL().equals(thisServer.getJMXServiceURL());
+    }
+
+}
\ No newline at end of file



Mime
View raw message