summaryrefslogtreecommitdiff
path: root/org/freedesktop/dbus/DirectConnection.java
blob: 62cf7e812556ed57a9858aa39dfc79893e904a0c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/*
   D-Bus Java Implementation
   Copyright (c) 2005-2006 Matthew Johnson

   This program is free software; you can redistribute it and/or modify it
   under the terms of either the GNU Lesser General Public License Version 2 or the
   Academic Free Licence Version 2.1.

   Full licence texts are included in the COPYING file with this program.
*/
package org.freedesktop.dbus;

import static org.freedesktop.dbus.Gettext._;

import java.lang.reflect.Proxy;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.Random;
import java.util.Vector;

import org.freedesktop.DBus;
import org.freedesktop.dbus.exceptions.DBusException;

import cx.ath.matthew.debug.Debug;

/** Handles a peer to peer connection between two applications withou a bus daemon.
 * <p>
 * Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues.
 * </p>
 */
public class DirectConnection extends AbstractConnection
{
   /**
    * Create a direct connection to another application.
    * @param address The address to connect to. This is a standard D-Bus address, except that the additional parameter 'listen=true' should be added in the application which is creating the socket.
    */
   public DirectConnection(String address) throws DBusException
   {
      super(address);

      try {
         transport = new Transport(addr, AbstractConnection.TIMEOUT);
      } catch (IOException IOe) {
         if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe);            
         throw new DBusException(_("Failed to connect to bus ")+IOe.getMessage());
      } catch (ParseException Pe) {
         if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Pe);            
         throw new DBusException(_("Failed to connect to bus ")+Pe.getMessage());
      }

      listen();
   }

   /**
    * Creates a bus address for a randomly generated tcp port.
    * @return a random bus address.
    */
   public static String createDynamicTCPSession()
   {
      String address = "tcp:host=localhost";
      int port;
      try {
         ServerSocket s = new ServerSocket();
         s.bind(null);
         port = s.getLocalPort();
         s.close();
      } catch (Exception e) {
         Random r = new Random();
         port = 32768 + (Math.abs(r.nextInt()) % 28232);
      }
      address += ",port="+port;
      address += ",guid="+Transport.genGUID();
      if (Debug.debug) Debug.print("Created Session address: "+address);
      return address;
   }

   /**
    * Creates a bus address for a randomly generated abstract unix socket.
    * @return a random bus address.
    */
   public static String createDynamicSession()
   {
      String address = "unix:";
      String path = "/tmp/dbus-XXXXXXXXXX";
      Random r = new Random();
      do {
         StringBuffer sb = new StringBuffer();
         for (int i = 0; i < 10; i++) 
            sb.append((char) ((Math.abs(r.nextInt()) % 26) + 65));
         path = path.replaceAll("..........$", sb.toString());
         if (Debug.debug) Debug.print(Debug.VERBOSE, "Trying path "+path);
      } while ((new File(path)).exists());
      address += "abstract="+path;
      address += ",guid="+Transport.genGUID();
      if (Debug.debug) Debug.print("Created Session address: "+address);
      return address;
   }
   DBusInterface dynamicProxy(String path) throws DBusException
   {
      try {
         DBus.Introspectable intro = (DBus.Introspectable) getRemoteObject(path, DBus.Introspectable.class);
         String data = intro.Introspect();
         String[] tags = data.split("[<>]");
         Vector<String> ifaces = new Vector<String>();
         for (String tag: tags) {
            if (tag.startsWith("interface")) {
               ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1"));
            }
         }
         Vector<Class<? extends Object>> ifcs = new Vector<Class<? extends Object>>();
         for(String iface: ifaces) {
            int j = 0;
            while (j >= 0) {
               try {
                  ifcs.add(Class.forName(iface));
                  break;
               } catch (Exception e) {}
               j = iface.lastIndexOf(".");
               char[] cs = iface.toCharArray();
               if (j >= 0) {
                  cs[j] = '$';
                  iface = String.valueOf(cs);
               }
            }
         }

         if (ifcs.size() == 0) throw new DBusException(_("Could not find an interface to cast to"));

         RemoteObject ro = new RemoteObject(null, path, null, false);
         DBusInterface newi =  (DBusInterface)
            Proxy.newProxyInstance(ifcs.get(0).getClassLoader(), 
                                   ifcs.toArray(new Class[0]),
                                   new RemoteInvocationHandler(this, ro));
         importedObjects.put(newi, ro);
         return newi;
      } catch (Exception e) {
         if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
         throw new DBusException(MessageFormat.format(_("Failed to create proxy object for {0}; reason: {1}."), new Object[] { path, e.getMessage()}));
      }
   }
   
   DBusInterface getExportedObject(String path) throws DBusException
   {
      ExportedObject o = null;
      synchronized (exportedObjects) {
         o = exportedObjects.get(path);
      }
      if (null != o && null == o.object.get()) {
         unExportObject(path);
         o = null;
      }
      if (null != o) return o.object.get();
      return dynamicProxy(path);
   }

   /** 
       * Return a reference to a remote object. 
       * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name.
       * In particular this means that if a process providing the well known name disappears and is taken over by another process
       * proxy objects gained by this method will make calls on the new proccess.
       * 
       * This method will use bus introspection to determine the interfaces on a remote object and so
       * <b>may block</b> and <b>may fail</b>. The resulting proxy object will, however, be castable
       * to any interface it implements. It will also autostart the process if applicable. Also note
       * that the resulting proxy may fail to execute the correct method with overloaded methods
       * and that complex types may fail in interesting ways. Basically, if something odd happens, 
       * try specifying the interface explicitly.
       * 
       * @param objectpath The path on which the process is exporting the object.
       * @return A reference to a remote object.
       * @throws ClassCastException If type is not a sub-type of DBusInterface
       * @throws DBusException If busname or objectpath are incorrectly formatted.
    */
   public DBusInterface getRemoteObject(String objectpath) throws DBusException
   {
      if (null == objectpath) throw new DBusException(_("Invalid object path: null"));
      
      if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) 
         throw new DBusException(_("Invalid object path: ")+objectpath);
      
      return dynamicProxy(objectpath);
   }

   /** 
       * Return a reference to a remote object. 
       * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name.
       * In particular this means that if a process providing the well known name disappears and is taken over by another process
       * proxy objects gained by this method will make calls on the new proccess.
       * @param objectpath The path on which the process is exporting the object.
       * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures
       * as the interface the remote object is exporting.
       * @return A reference to a remote object.
       * @throws ClassCastException If type is not a sub-type of DBusInterface
       * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package.
    */
   public DBusInterface getRemoteObject(String objectpath, Class<? extends DBusInterface> type) throws DBusException
   {
      if (null == objectpath) throw new DBusException(_("Invalid object path: null"));
      if (null == type) throw new ClassCastException(_("Not A DBus Interface"));
      
      if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) 
         throw new DBusException(_("Invalid object path: ")+objectpath);
      
      if (!DBusInterface.class.isAssignableFrom(type)) throw new ClassCastException(_("Not A DBus Interface"));

      // don't let people import things which don't have a
      // valid D-Bus interface name
      if (type.getName().equals(type.getSimpleName()))
         throw new DBusException(_("DBusInterfaces cannot be declared outside a package"));
      
      RemoteObject ro = new RemoteObject(null, objectpath, type, false);
      DBusInterface i =  (DBusInterface) Proxy.newProxyInstance(type.getClassLoader(), 
            new Class[] { type }, new RemoteInvocationHandler(this, ro));
      importedObjects.put(i, ro);
      return i;
   }
   protected <T extends DBusSignal> void removeSigHandler(DBusMatchRule rule, DBusSigHandler<T> handler) throws DBusException
   {
      SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource());
      synchronized (handledSignals) {
         Vector<DBusSigHandler<? extends DBusSignal>> v = handledSignals.get(key);
         if (null != v) {
            v.remove(handler);
            if (0 == v.size()) {
               handledSignals.remove(key);
            }
         } 
      }
   }
   protected <T extends DBusSignal> void addSigHandler(DBusMatchRule rule, DBusSigHandler<T> handler) throws DBusException
   {
      SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource());
      synchronized (handledSignals) {
         Vector<DBusSigHandler<? extends DBusSignal>> v = handledSignals.get(key);
         if (null == v) {
            v = new Vector<DBusSigHandler<? extends DBusSignal>>();
            v.add(handler);
            handledSignals.put(key, v);
         } else
            v.add(handler);
      }
   }
   DBusInterface getExportedObject(String source, String path) throws DBusException
   {
      return getExportedObject(path);
   }
}