1package org.jivesoftware.smackx;
2
3import java.util.HashMap;
4import java.util.Iterator;
5import java.util.List;
6import java.util.Map;
7
8import org.jivesoftware.smack.Connection;
9import org.jivesoftware.smack.PacketCollector;
10import org.jivesoftware.smack.PacketListener;
11import org.jivesoftware.smack.Roster;
12import org.jivesoftware.smack.RosterEntry;
13import org.jivesoftware.smack.SmackConfiguration;
14import org.jivesoftware.smack.XMPPException;
15import org.jivesoftware.smack.filter.PacketIDFilter;
16import org.jivesoftware.smack.filter.PacketTypeFilter;
17import org.jivesoftware.smack.packet.IQ;
18import org.jivesoftware.smack.packet.Packet;
19import org.jivesoftware.smack.packet.Presence;
20import org.jivesoftware.smack.packet.Registration;
21import org.jivesoftware.smack.util.StringUtils;
22import org.jivesoftware.smackx.packet.DiscoverInfo;
23import org.jivesoftware.smackx.packet.DiscoverInfo.Identity;
24
25/**
26 * This class provides an abstract view to gateways/transports. This class handles all
27 * actions regarding gateways and transports.
28 * @author Till Klocke
29 *
30 */
31public class Gateway {
32
33	private Connection connection;
34	private ServiceDiscoveryManager sdManager;
35	private Roster roster;
36	private String entityJID;
37	private Registration registerInfo;
38	private Identity identity;
39	private DiscoverInfo info;
40
41	Gateway(Connection connection, String entityJID){
42		this.connection = connection;
43		this.roster = connection.getRoster();
44		this.sdManager = ServiceDiscoveryManager.getInstanceFor(connection);
45		this.entityJID = entityJID;
46	}
47
48	Gateway(Connection connection, String entityJID, DiscoverInfo info, Identity identity){
49		this(connection, entityJID);
50		this.info = info;
51		this.identity = identity;
52	}
53
54	private void discoverInfo() throws XMPPException{
55		info = sdManager.discoverInfo(entityJID);
56		Iterator<Identity> iterator = info.getIdentities();
57		while(iterator.hasNext()){
58			Identity temp = iterator.next();
59			if(temp.getCategory().equalsIgnoreCase("gateway")){
60				this.identity = temp;
61				break;
62			}
63		}
64	}
65
66	private Identity getIdentity() throws XMPPException{
67		if(identity==null){
68			discoverInfo();
69		}
70		return identity;
71	}
72
73	private Registration getRegisterInfo(){
74		if(registerInfo==null){
75			refreshRegisterInfo();
76		}
77		return registerInfo;
78	}
79
80	private void refreshRegisterInfo(){
81		Registration packet = new Registration();
82		packet.setFrom(connection.getUser());
83		packet.setType(IQ.Type.GET);
84		packet.setTo(entityJID);
85		PacketCollector collector =
86			connection.createPacketCollector(new PacketIDFilter(packet.getPacketID()));
87		connection.sendPacket(packet);
88		Packet result = collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
89		collector.cancel();
90		if(result instanceof Registration && result.getError()==null){
91			Registration register = (Registration)result;
92			this.registerInfo = register;
93		}
94	}
95
96	/**
97	 * Checks if this gateway supports In-Band registration
98	 * @return true if In-Band registration is supported
99	 * @throws XMPPException
100	 */
101	public boolean canRegister() throws XMPPException{
102		if(info==null){
103			discoverInfo();
104		}
105		return info.containsFeature("jabber:iq:register");
106	}
107
108	/**
109	 * Returns all fields that are required to register to this gateway
110	 * @return a list of required fields
111	 */
112	public List<String> getRequiredFields(){
113		return getRegisterInfo().getRequiredFields();
114	}
115
116	/**
117	 * Returns the name as proposed in this gateways identity discovered via service
118	 * discovery
119	 * @return a String of its name
120	 * @throws XMPPException
121	 */
122	public String getName() throws XMPPException{
123		if(identity==null){
124			discoverInfo();
125		}
126		return identity.getName();
127	}
128
129	/**
130	 * Returns the type as proposed in this gateways identity discovered via service
131	 * discovery. See {@link http://xmpp.org/registrar/disco-categories.html} for
132	 * possible types
133	 * @return a String describing the type
134	 * @throws XMPPException
135	 */
136	public String getType() throws XMPPException{
137		if(identity==null){
138			discoverInfo();
139		}
140		return identity.getType();
141	}
142
143	/**
144	 * Returns true if the registration informations indicates that you are already
145	 * registered with this gateway
146	 * @return true if already registered
147	 * @throws XMPPException
148	 */
149	public boolean isRegistered() throws XMPPException{
150		return getRegisterInfo().isRegistered();
151	}
152
153	/**
154	 * Returns the value of specific field of the registration information. Can be used
155	 * to retrieve for example to retrieve username/password used on an already registered
156	 * gateway.
157	 * @param fieldName name of the field
158	 * @return a String containing the value of the field or null
159	 */
160	public String getField(String fieldName){
161		return getRegisterInfo().getField(fieldName);
162	}
163
164	/**
165	 * Returns a List of Strings of all field names which contain values.
166	 * @return a List of field names
167	 */
168	public List<String> getFieldNames(){
169		return getRegisterInfo().getFieldNames();
170	}
171
172	/**
173	 * A convenience method for retrieving the username of an existing account
174	 * @return String describing the username
175	 */
176	public String getUsername(){
177		return getField("username");
178	}
179
180	/**
181	 * A convenience method for retrieving the password of an existing accoung
182	 * @return String describing the password
183	 */
184	public String getPassword(){
185		return getField("password");
186	}
187
188	/**
189	 * Returns instructions for registering with this gateway
190	 * @return String containing instructions
191	 */
192	public String getInstructions(){
193		return getRegisterInfo().getInstructions();
194	}
195
196	/**
197	 * With this method you can register with this gateway or modify an existing registration
198	 * @param username String describing the username
199	 * @param password String describing the password
200	 * @param fields additional fields like email.
201	 * @throws XMPPException
202	 */
203	public void register(String username, String password, Map<String,String> fields)throws XMPPException{
204		if(getRegisterInfo().isRegistered()) {
205			throw new IllegalStateException("You are already registered with this gateway");
206		}
207		Registration register = new Registration();
208		register.setFrom(connection.getUser());
209		register.setTo(entityJID);
210		register.setType(IQ.Type.SET);
211		register.setUsername(username);
212		register.setPassword(password);
213		for(String s : fields.keySet()){
214			register.addAttribute(s, fields.get(s));
215		}
216		PacketCollector resultCollector =
217			connection.createPacketCollector(new PacketIDFilter(register.getPacketID()));
218		connection.sendPacket(register);
219		Packet result =
220			resultCollector.nextResult(SmackConfiguration.getPacketReplyTimeout());
221		resultCollector.cancel();
222		if(result!=null && result instanceof IQ){
223			IQ resultIQ = (IQ)result;
224			if(resultIQ.getError()!=null){
225				throw new XMPPException(resultIQ.getError());
226			}
227			if(resultIQ.getType()==IQ.Type.ERROR){
228				throw new XMPPException(resultIQ.getError());
229			}
230			connection.addPacketListener(new GatewayPresenceListener(),
231					new PacketTypeFilter(Presence.class));
232			roster.createEntry(entityJID, getIdentity().getName(), new String[]{});
233		}
234		else{
235			throw new XMPPException("Packet reply timeout");
236		}
237	}
238
239	/**
240	 * A convenience method for registering or modifying an account on this gateway without
241	 * additional fields
242	 * @param username String describing the username
243	 * @param password String describing the password
244	 * @throws XMPPException
245	 */
246	public void register(String username, String password) throws XMPPException{
247		register(username, password,new HashMap<String,String>());
248	}
249
250	/**
251	 * This method removes an existing registration from this gateway
252	 * @throws XMPPException
253	 */
254	public void unregister() throws XMPPException{
255		Registration register = new Registration();
256		register.setFrom(connection.getUser());
257		register.setTo(entityJID);
258		register.setType(IQ.Type.SET);
259		register.setRemove(true);
260		PacketCollector resultCollector =
261			connection.createPacketCollector(new PacketIDFilter(register.getPacketID()));
262		connection.sendPacket(register);
263		Packet result = resultCollector.nextResult(SmackConfiguration.getPacketReplyTimeout());
264		resultCollector.cancel();
265		if(result!=null && result instanceof IQ){
266			IQ resultIQ = (IQ)result;
267			if(resultIQ.getError()!=null){
268				throw new XMPPException(resultIQ.getError());
269			}
270			if(resultIQ.getType()==IQ.Type.ERROR){
271				throw new XMPPException(resultIQ.getError());
272			}
273			RosterEntry gatewayEntry = roster.getEntry(entityJID);
274			roster.removeEntry(gatewayEntry);
275		}
276		else{
277			throw new XMPPException("Packet reply timeout");
278		}
279	}
280
281	/**
282	 * Lets you login manually in this gateway. Normally a gateway logins you when it
283	 * receives the first presence broadcasted by your server. But it is possible to
284	 * manually login and logout by sending a directed presence. This method sends an
285	 * empty available presence direct to the gateway.
286	 */
287	public void login(){
288		Presence presence = new Presence(Presence.Type.available);
289		login(presence);
290	}
291
292	/**
293	 * This method lets you send the presence direct to the gateway. Type, To and From
294	 * are modified.
295	 * @param presence the presence used to login to gateway
296	 */
297	public void login(Presence presence){
298		presence.setType(Presence.Type.available);
299		presence.setTo(entityJID);
300		presence.setFrom(connection.getUser());
301		connection.sendPacket(presence);
302	}
303
304	/**
305	 * This method logs you out from this gateway by sending an unavailable presence
306	 * to directly to this gateway.
307	 */
308	public void logout(){
309		Presence presence = new Presence(Presence.Type.unavailable);
310		presence.setTo(entityJID);
311		presence.setFrom(connection.getUser());
312		connection.sendPacket(presence);
313	}
314
315	private class GatewayPresenceListener implements PacketListener{
316
317		public void processPacket(Packet packet) {
318			if(packet instanceof Presence){
319				Presence presence = (Presence)packet;
320				if(entityJID.equals(presence.getFrom()) &&
321						roster.contains(presence.getFrom()) &&
322						presence.getType().equals(Presence.Type.subscribe)){
323					Presence response = new Presence(Presence.Type.subscribed);
324					response.setTo(presence.getFrom());
325					response.setFrom(StringUtils.parseBareAddress(connection.getUser()));
326					connection.sendPacket(response);
327				}
328			}
329
330		}
331	}
332
333}
334