001: /*
002: * Portions Copyright 2000-2007 Sun Microsystems, Inc. All Rights
003: * Reserved. Use is subject to license terms.
004: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU General Public License version
008: * 2 only, as published by the Free Software Foundation.
009: *
010: * This program is distributed in the hope that it will be useful, but
011: * WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * General Public License version 2 for more details (a copy is
014: * included at /legal/license.txt).
015: *
016: * You should have received a copy of the GNU General Public License
017: * version 2 along with this work; if not, write to the Free Software
018: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
019: * 02110-1301 USA
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
022: * Clara, CA 95054 or visit www.sun.com if you need additional
023: * information or have any questions.
024: */
025:
026: package javax.microedition.sip;
027:
028: import java.io.IOException;
029: import java.io.OutputStream;
030: import java.lang.IllegalArgumentException;
031: import javax.microedition.sip.SipException;
032:
033: import gov.nist.core.ParseException;
034: import gov.nist.microedition.sip.RefreshManager;
035: import gov.nist.microedition.sip.RefreshTask;
036: import gov.nist.microedition.sip.StackConnector;
037: import gov.nist.microedition.sip.SipClientConnectionImpl;
038: import gov.nist.siplite.address.Address;
039: import gov.nist.siplite.header.Header;
040: import gov.nist.siplite.header.CallIdHeader;
041: import gov.nist.siplite.header.ContactHeader;
042: import gov.nist.siplite.header.ContactList;
043: import gov.nist.siplite.header.ContentLengthHeader;
044: import gov.nist.siplite.header.ContentTypeHeader;
045: import gov.nist.siplite.header.ExpiresHeader;
046: import gov.nist.siplite.header.CSeqHeader;
047: import gov.nist.siplite.message.Request;
048:
049: import com.sun.midp.log.Logging;
050: import com.sun.midp.log.LogChannels;
051:
052: /**
053: * This class implements the functionality that facilitates the handling of
054: * refreshing requests on behalf of the application.
055: * @see JSR180 spec, v 1.0.1, p 58-61
056: *
057: */
058: public class SipRefreshHelper {
059: /**
060: * The unique instance of this class
061: */
062: private static SipRefreshHelper instance = null;
063: /** Refresh manager. */
064: private static RefreshManager refreshManager = null;
065:
066: /**
067: * Hide default constructor.
068: */
069: private SipRefreshHelper() {
070: }
071:
072: /**
073: * Returns the instance of SipRefreshHelper
074: * @return the instance of SipRefreshHelper singleton
075: */
076: public static javax.microedition.sip.SipRefreshHelper getInstance() {
077: if (instance == null) {
078: instance = new SipRefreshHelper();
079: refreshManager = RefreshManager.getInstance();
080: }
081:
082: return instance;
083: }
084:
085: /**
086: * Stops refreshing a specific request related to refeshID.
087: * @see JSR180 spec, v 1.0.1, p 60
088: *
089: * @param refreshID the ID of the refresh to be stopped. If the ID
090: * does not match any refresh task the method does nothing.
091: */
092: public void stop(int refreshID) {
093: RefreshTask refreshTask = getRefreshTask(refreshID);
094: if (refreshTask == null) {
095: return;
096: }
097:
098: Request requestNotCloned = refreshTask.getRequest();
099: Request request = (Request) requestNotCloned.clone();
100: requestNotCloned = null;
101: removeBindings(refreshTask, request);
102:
103: doStop(refreshID, refreshTask);
104: }
105:
106: /**
107: * Updates one refreshed request with new values.
108: * @see JSR180 spec, v 1.0.1, p 60
109: *
110: * @param type value of Content-Type (null or empty, no content)
111: * @param length value of Content-Length (<=0, no content)
112: * @param expires value of Expires (-1, no Expires header),
113: * (0, stop the refresh)
114: * @return Returns the OutputStream to fill the content. If the update does
115: * not have new content (type = null and/or length = 0) method returns null
116: * and the message is sent automatically.
117: * @throws java.lang.IllegalArgumentException if some input parameter
118: * is invalid
119: */
120: public java.io.OutputStream update(int refreshID,
121: java.lang.String[] contact, java.lang.String type,
122: int length, int expires) {
123: String taskId;
124: try {
125: taskId = String.valueOf(refreshID);
126: } catch (NumberFormatException nfe) {
127: return null;
128: }
129:
130: RefreshTask refreshTask = refreshManager.getTask(taskId);
131: if (refreshTask == null) {
132: return null;
133: }
134:
135: // System.out.println(">>> update, type = " + type);
136:
137: Request requestNotCloned = refreshTask.getRequest();
138: Request request = (Request) requestNotCloned.clone();
139: requestNotCloned = null;
140:
141: // Contacts
142: if (contact != null && contact.length != 0) {
143: // JSR180: contact replaces all old values.
144: // Multiple Contact header values are applicable
145: // only for REGISTER method.
146: if (contact.length > 1
147: && !request.getMethod().equals(Request.REGISTER)) {
148: throw new IllegalArgumentException(
149: "Only one contact is allowed for non-REGISTER requests.");
150: }
151:
152: // Remove old contacts from the request.
153: ContactList cl = request.getContactHeaders();
154: int n = (cl != null) ? cl.size() : 0;
155:
156: for (int i = 0; i < n; i++) {
157: request.removeHeader(ContactHeader.NAME);
158: }
159:
160: // Add new contacts to the request.
161: for (int i = 0; i < contact.length; i++) {
162: String contactURI = contact[i];
163: Address address = null;
164:
165: try {
166: address = StackConnector.addressFactory
167: .createAddress(contactURI);
168: } catch (ParseException pe) {
169: throw new IllegalArgumentException(
170: "one of the contact "
171: + "addresses is not valid");
172: }
173:
174: try {
175: ContactHeader contactHeader = StackConnector.headerFactory
176: .createContactHeader(address);
177: request.addHeader(contactHeader);
178: } catch (SipException ex) {
179: throw new IllegalArgumentException(ex.getMessage());
180: }
181: }
182: }
183:
184: // Expires
185: if (expires == -1) {
186: request.removeHeader(ExpiresHeader.NAME);
187: } else if (expires >= 0) {
188: if (expires == 0) {
189: // Stop the refresh
190: removeBindings(refreshTask, request);
191: doStop(refreshID, refreshTask);
192:
193: // Ambiguousness in the JSR180 spec:
194: // "expires = 0 has the same effect as calling stop(refreshID)"
195: // But: "update() returns the OutputStream to fill the content.
196: // If the update does not have new content (type = null and/or
197: // length = 0) method returns null and the message is sent
198: // automatically".
199: // It is not clear what to do if the expires is 0 and the
200: // content is not null. If the refresh is stopped, we can't
201: // provide the OutputStream to fill the content, so return null.
202: return null;
203: }
204:
205: ExpiresHeader eh = (ExpiresHeader) request
206: .getHeader(ExpiresHeader.NAME);
207: if (eh == null) {
208: // Try to add an ExpiresHeader
209: try {
210: eh = StackConnector.headerFactory
211: .createExpiresHeader(expires);
212: request.addHeader(eh);
213: } catch (SipException ex) {
214: throw new IllegalArgumentException(ex.getMessage());
215: }
216: }
217:
218: eh.setExpires(expires);
219: } else {
220: throw new IllegalArgumentException(
221: "the expires value is not correct");
222: }
223:
224: // Content Length
225: ContentLengthHeader contentLengthHeader = request
226: .getContentLengthHeader();
227:
228: if (contentLengthHeader == null) {
229: try {
230: request.addHeader(StackConnector.headerFactory
231: .createContentLengthHeader(0));
232: } catch (SipException ex) {
233: throw new IllegalArgumentException(ex.getMessage());
234: }
235: }
236:
237: // Content Type
238: if (length > 0 && type != null && !type.equals("")) {
239: request.removeHeader(Header.CONTENT_TYPE);
240:
241: Exception ex = null;
242: try {
243: ContentTypeHeader contentTypeHeader = (ContentTypeHeader) StackConnector.headerFactory
244: .createHeader(Header.CONTENT_TYPE, type);
245: request.addHeader(contentTypeHeader);
246: } catch (ParseException pe) {
247: ex = pe;
248: } catch (SipException se) {
249: ex = se;
250: }
251: if (ex != null) {
252: throw new IllegalArgumentException(ex.getMessage());
253: }
254:
255: request.getContentLengthHeader().setContentLength(length);
256: }
257:
258: // Call-Id header was added in SipClientConnectionImpl.send()
259: // when the initial request was sent. This is the reason why
260: // Call-Id header is not added here.
261:
262: // Cancel the timer
263: refreshTask.cancel();
264:
265: SipClientConnectionImpl sipClientConnection = (SipClientConnectionImpl) refreshTask
266: .getSipClientConnection();
267:
268: if (type == null || type.equals("") || length <= 0) {
269: // If the is no new content, send the message automatically
270: // Don't call sipClientConnection.send() directly
271: // because CSeq header must be updated.
272: try {
273: refreshTask.updateAndSendRequest(request);
274: } catch (IOException ex) {
275: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
276: Logging.report(Logging.WARNING,
277: LogChannels.LC_JSR180,
278: "SipRefreshHelper.update(): " + ex);
279: }
280: }
281:
282: return null;
283: } else {
284: OutputStream contentOutputStream = null;
285:
286: try {
287: // set the content
288: // contentOutputStream = sipClientConnection
289: // .openContentOutputStream();
290: contentOutputStream = refreshTask
291: .updateRequestAndOpenOutputStream(request);
292: } catch (IOException ioe) {
293: return null;
294: }
295:
296: return contentOutputStream;
297: }
298: }
299:
300: /**
301: * Finds a refresh task corresponding to the given refreshID.
302: * @param refreshID the ID of the refresh.
303: * @return RefreshTask object that corresponds to the given refreshID
304: * or null if there is no such task.
305: */
306: private RefreshTask getRefreshTask(int refreshID) {
307: String taskId;
308:
309: try {
310: taskId = String.valueOf(refreshID);
311: } catch (NumberFormatException nfe) {
312: return null;
313: }
314:
315: return refreshManager.getTask(taskId);
316: }
317:
318: /**
319: * Stops refreshing a specific request related to refeshID without
320: * removing the possible binding between end point and registrar/notifier.
321: * @param refreshID the ID of the refresh to be stopped. If the ID
322: * does not match any refresh task the method does nothing.
323: * @param refreshTask refresh task that corresponds to the given refreshID.
324: */
325: private void doStop(int refreshID, RefreshTask refreshTask) {
326: String taskId;
327:
328: try {
329: taskId = String.valueOf(refreshID);
330: } catch (NumberFormatException nfe) {
331: return;
332: }
333:
334: // Cancel the timer
335: refreshTask.cancel();
336: refreshTask.getSipRefreshListener().refreshEvent(refreshID, 0,
337: "refresh stopped");
338: refreshManager.removeTask(taskId);
339: }
340:
341: /**
342: * Removes the possible binding between end point and registrar/notifier
343: * as described in RFC 3261, section 10.2.2, Removing Bindings:
344: * Registrations are soft state and expire unless refreshed, but can also
345: * be explicitly removed. A client can attempt to influence the expiration
346: * interval selected by the registrar as described in Section 10.2.1. A UA
347: * requests the immediate removal of a binding by specifying an expiration
348: * interval of "0" for that contact address in a REGISTER request.
349: * @param refreshTask the refresh task that refreshes the given request.
350: * @param request the request that will be sent with the header Expires = 0.
351: */
352: private void removeBindings(RefreshTask refreshTask, Request request) {
353: ExpiresHeader eh = (ExpiresHeader) request
354: .getHeader(ExpiresHeader.NAME);
355:
356: if (eh == null) {
357: Exception ex = null;
358: // Try to add an ExpiresHeader
359: try {
360: eh = StackConnector.headerFactory
361: .createExpiresHeader(0);
362: request.addHeader(eh);
363: } catch (SipException se) {
364: ex = se;
365: } catch (IllegalArgumentException iae) {
366: ex = iae;
367: }
368: if (ex != null) {
369: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
370: Logging.report(Logging.ERROR,
371: LogChannels.LC_JSR180,
372: "SipRefreshHelper.removeBindings(): " + ex);
373: }
374: return;
375: }
376: }
377:
378: eh.setExpires(0);
379:
380: // Update the request of the sipClientConnection
381: // and send it immediately.
382:
383: try {
384: refreshTask.updateAndSendRequest(request);
385: } catch (IOException ex) {
386: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
387: Logging.report(Logging.WARNING, LogChannels.LC_JSR180,
388: "SipRefreshHelper.removeBindings(): " + ex);
389: }
390: }
391: }
392: }
|