/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.sip;
import com.ericsson.ssa.sip.dns.EnumUtil;
import com.sun.enterprise.util.LocalStringManagerImpl;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.TelURL;
/**
* Implementation of the TelURL.
*
*
* @author ekrigro
* @since 2005-maj-04
* @reviewed qmigkra 2007-jan-17
*
*/
public class TelURLImpl extends URIImpl implements TelURL, Serializable {
private static final long serialVersionUID = 3690194326332258097L;
static String NO_NUMBER = "The parsed TEL URL did not cantain any number";
static String DUPLICATE_PARAM = "The parsed TEL URL has two parameter with the same name : ";
static String PHONE_CTX_MISSING = "The parsed TEL URL doesn't contain the mandatory parameter phone-context";
public static String PHONE_CONTEXT = "phone-context";
protected static final LocalStringManagerImpl localStrings = new LocalStringManagerImpl(SipSessionImplBase.class);
private boolean _global = false;
private String _number = null;
private TelURLImpl() {
}
public TelURLImpl(String uri, int offset) throws ServletParseException {
parse(uri, offset);
}
public String getPhoneNumber() {
return _number;
}
public void setPhoneNumber(String number) {
verify();
ParseResult pr = parsePhoneNumber(number, 0);
validateAndSetParsetResult(pr);
}
public void setPhoneNumber(String number, String phoneContext) {
verify();
ParseResult pr = parsePhoneNumber(number, 0);
if (pr.global) {
String msg = localStrings.getLocalString("number_cannotbe_global",
"The Telephone number cannot be global.");
throw new IllegalArgumentException(msg);
}
pr.phoneContext = phoneContext;
validateAndSetParsetResult(pr);
}
public String getPhoneContext() {
if (_global) {
return null;
}
return getParameter(PHONE_CONTEXT);
}
public boolean isGlobal() {
return _global;
}
public TelURLImpl clone() {
TelURLImpl uri = new TelURLImpl();
uri._global = _global;
uri._number = _number;
uri._parameters = (ParameterByteMap) _parameters.clone();
return uri;
}
/**
* The comparison is done according to RFC 3966, chapter 4 "URI Comparisons"
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o) {
if (o == null) {
return false;
}
TelURL uri;
try {
uri = (TelURL) o;
} catch (Exception e) {
return false;
}
if (_global != uri.isGlobal()) {
return false;
}
// Remove visual separators
String tel1 = EnumUtil.getAUSValue(_number);
String tel2 = EnumUtil.getAUSValue(uri.getPhoneNumber());
// and compare
if (!tel1.equals(tel2)) {
return false;
}
if (!_global) {
// context = ";phone-context=" descriptor
// descriptor = domainname / global-number-digits
// global-number-digits = "+" *phonedigit DIGIT *phonedigit
// local-number-digits =
// *phonedigit-hex (HEXDIG / "*" / "#")*phonedigit-hex
// domainname = *( domainlabel "." ) toplabel [ "." ]
// domainlabel = alphanum
// / alphanum *( alphanum / "-" ) alphanum
// toplabel = ALPHA / ALPHA *( alphanum / "-" ) alphanum
// Compare the phone-context
String phoneContext1 = getParameter(PHONE_CONTEXT);
String phoneContext2 = uri.getParameter(PHONE_CONTEXT);
// If it is a global number
if (phoneContext1.startsWith("+") && phoneContext2.startsWith("+")) {
phoneContext1 = EnumUtil.getAUSValue(phoneContext1);
phoneContext2 = EnumUtil.getAUSValue(phoneContext2);
if (!phoneContext1.equals(phoneContext2)) {
return false;
}
} else { // else it is a domain
if (!phoneContext1.equals(phoneContext2)) {
return false;
}
}
}
HashMap<String, String> map1 = new HashMap<String, String>(8);
Iterator<String> thisParameters = _parameters.getKeys();
while (thisParameters.hasNext()) {
String parameter = (String) thisParameters.next();
String value = (String) getParameter(parameter);
// Do not compare phone-context
if (!parameter.equals(PHONE_CONTEXT)) {
map1.put(parameter, value);
}
}
HashMap<String, String> map2 = new HashMap<String, String>(8);
Iterator<String> uriParameters = uri.getParameterNames();
while (uriParameters.hasNext()) {
String parameter = (String) uriParameters.next();
String value = (String) uri.getParameter(parameter);
// Do not compare phone-context
if (!parameter.equals(PHONE_CONTEXT)) {
map2.put(parameter, value);
}
}
if (!map1.equals(map2)) {
return false;
}
return true;
// if (!_global) {
// ParameterByteMap map1 = (ParameterByteMap) _parameters.clone();
// ParameterByteMap map2 = (ParameterByteMap) uri._parameters.clone();
//
// // context = ";phone-context=" descriptor
// // descriptor = domainname / global-number-digits
// // global-number-digits = "+" *phonedigit DIGIT *phonedigit
// // local-number-digits =
// // *phonedigit-hex (HEXDIG / "*" / "#")*phonedigit-hex
// // domainname = *( domainlabel "." ) toplabel [ "." ]
// // domainlabel = alphanum
// // / alphanum *( alphanum / "-" ) alphanum
// // toplabel = ALPHA / ALPHA *( alphanum / "-" ) alphanum
// String phoneContext1 = map1.get(PHONE_CONTEXT);
// map1.remove(PHONE_CONTEXT);
//
// String phoneContext2 = map2.get(PHONE_CONTEXT);
// map2.remove(PHONE_CONTEXT);
//
// // If it is a global number
// if (phoneContext1.startsWith("+") && phoneContext2.startsWith("+")) {
// phoneContext1 = EnumUtil.getAUSValue(phoneContext1);
// phoneContext2 = EnumUtil.getAUSValue(phoneContext2);
//
// if (!phoneContext1.equals(phoneContext2)) {
// return false;
// }
// } else { // else it is a domain
//
// if (!phoneContext1.equals(phoneContext2)) {
// return false;
// }
// }
//
// if (!map1.equals(map2)) {
// return false;
// }
// } else {
// if (!_parameters.equals(uri._parameters)) {
// return false;
// }
// }
//
// return true;
}
private void validateCharacter(char c, ParseResult pr) {
char[] validChars = { '-', '.', '(', ')' };
char[] validHexChars = { 'A', 'B', 'C', 'D', 'E', 'F' };
char[] validLocalChars = { '*', '#' };
if (Character.isDigit(c)) {
pr.digitFound = true;
return;
}
for (char valid : validChars) {
if (valid == c) {
return;
}
}
if (!pr.global) {
for (char valid : validHexChars) {
if (valid == c) {
pr.digitFound = true;
return;
}
}
for (char valid : validLocalChars) {
if (valid == c) {
return;
}
}
}
String msg = localStrings.getLocalString("number_contains_invalidchar",
"The Telephone number contains and invalid character :" + c,
new Object[] { "" + c });
throw new IllegalArgumentException(msg);
}
private ParseResult parsePhoneNumber(String str, int offset) {
int mark = offset;
int current = offset;
int end = str.length();
char c = 0;
ParseResult pr = new ParseResult();
if (current < end) {
c = str.charAt(current++);
}
if (c == '+') {
pr.global = true;
mark = current;
if (current < end) {
c = str.charAt(current++);
validateCharacter(c, pr);
}
}
//int restCurrent = -1;
//int restEnd = -1;
while (current < end) {
if (c == ';') {
//restCurrent = current;
//restEnd = end;
pr.offset = current;
current--;
break;
} else {
validateCharacter(c, pr);
c = str.charAt(current++);
}
}
pr.phoneNumber = str.substring(mark, current);
return pr;
}
private void parseParameters(String str, ParseResult pr)
throws ServletParseException {
int restCurrent = pr.offset;
int restEnd = str.length();
char c = 0;
if (restCurrent != -1) {
String pname = null;
StringBuilder temp = new StringBuilder();
StringBuilder pvalue = new StringBuilder();
while (restCurrent < restEnd) {
c = str.charAt(restCurrent++);
if (c == '=') {
pname = temp.toString();
} else if (c == ';') {
setParameter(pname, pvalue, pr);
pname = null;
temp = new StringBuilder();
pvalue = new StringBuilder();
} else if (pname == null) {
temp.append(c);
} else {
pvalue.append(c);
}
}
if (pname != null) {
setParameter(pname, pvalue, pr);
}
}
}
private void setParameter(String pname, StringBuilder pvalue, ParseResult pr)
throws ServletParseException {
if (_parameters.get(pname) != null) {
throw new ServletParseException(DUPLICATE_PARAM + pname);
}
if (!pr.global && pname.equals(PHONE_CONTEXT)) {
pr.phoneContext = pvalue.toString();
} else {
_parameters.put(pname, pvalue.toString());
}
}
private void validateAndSetParsetResult(ParseResult pr) {
if ((pr.phoneNumber == null) || (pr.phoneNumber.length() == 0)) {
String msg = localStrings.getLocalString("number_cannotbe_null",
"The Telephone number cannot be null.");
throw new IllegalArgumentException(msg);
}
//Do we need this strictness checking?
if (!pr.digitFound) {
String msg = localStrings.getLocalString("number_shouldcontain_onedigit",
"The Telephone number should contain atleast one digit.");
throw new IllegalArgumentException(msg);
}
if (!pr.global && (pr.phoneContext == null)) {
String msg = localStrings.getLocalString("context_cantbe_null",
"For a local number, phone context cannot be null.");
throw new IllegalArgumentException(msg);
}
_number = pr.phoneNumber;
_global = pr.global;
if (!_global) {
setParameter(PHONE_CONTEXT, pr.phoneContext);
}
}
private void parse(String str, int offset) throws ServletParseException {
ParseResult pr = parsePhoneNumber(str, offset);
parseParameters(str, pr);
try {
validateAndSetParsetResult(pr);
} catch (IllegalArgumentException iae) {
ServletParseException spe = new ServletParseException(iae.getMessage());
throw ServletParseException.class.cast(spe.initCause(iae));
}
}
public String getScheme() {
return SipFactoryImpl.TEL_URI_PROTOCOL;
}
public boolean isSipURI() {
return false;
}
public String toString() {
StringBuilder sb = new StringBuilder("tel:");
addBody(sb);
return sb.toString();
}
private void addBody(StringBuilder sb) {
if (_global) {
sb.append('+');
}
sb.append(_number);
if (_parameters != null) {
Iterator<String> i = _parameters.getKeys();
while (i.hasNext()) {
String pname = i.next();
sb.append(';');
sb.append(pname);
sb.append('=');
sb.append(_parameters.get(pname));
}
}
}
/**
* Gets the string representation of this Tel-URL as it should be specified in a
* SIP-URI user.
* @return the string representation of this Tel-URL as it should be specified in a
* SIP-URI user
*/
public String getAsSipUriUser() {
StringBuilder sb = new StringBuilder();
addBody(sb);
return sb.toString();
}
class ParseResult {
int offset = -1;
boolean global;
String phoneNumber;
String phoneContext;
boolean digitFound;
}
}