blob: 95d20de29c0e5743a4f8b5b31227f830e1d73c26 [file] [log] [blame]
/* 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.
*/
package org.apache.qpid.client.url;
import org.apache.qpid.client.BrokerDetails;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
/**
* The format Qpid URL is based on the AMQP one. The grammar is as follows:
* <p>
* <p>{@literal qpid_url = "qpid:" [client_props "@"] port_addr_list ["/" future-parameters] }
* <p>{@literal port_addr_list = [port_addr ","]* port_addr }
* <p>{@literal port_addr = tcp_port_addr | tls_prot_addr | future_prot_addr }
* <p>{@literal tcp_port_addr = tcp_id tcp_addr }
* <p>{@literal tcp_id = "tcp:" | "" }
* <p>{@literal tcp_addr = host [":" port] }
* <p>{@literal host = <as per http://www.apps.ietf.org/> }
* <p>{@literal port = number }
* <p>{@literal tls_prot_addr = tls_id tls_addr }
* <p>{@literal tls_id = "tls:" | "" }
* <p>{@literal tls_addr = host [":" port] }
* <p>{@literal future_prot_addr = future_prot_id future_prot_addr }
* <p>{@literal future_prot_id = <placeholder, must end in ":". Example "sctp:"> }
* <p>{@literal future_prot_addr = <placeholder, protocl-specific address> }
* <p>{@literal future_parameters = <placeholder, not used in failover addresses> }
* <p>{@literal client_props = [client_prop ";"]* client_prop }
* <p>{@literal client_prop = prop "=" val }
* <p>{@literal prop = chars as per <as per http://www.apps.ietf.org/> }
* <p>{@literal val = valid as per <as per http://www.apps.ietf.org/> }
* <p>
* Ex:
* <p>
* {@literal qpid:virtualhost=tcp:host-foo,test,client_id=foo@tcp:myhost.com:5672,virtualhost=prod;
* keystore=/opt/keystore@client_id2@tls:mysecurehost.com:5672 }
*/
public class URLParser_0_10
{
private static final char[] URL_START_SEQ = new char[]{'q', 'p', 'i', 'd', ':'};
private static final char PROPERTY_EQUALS_CHAR = '=';
private static final char PROPERTY_SEPARATOR_CHAR = ';';
private static final char ADDRESS_SEPERATOR_CHAR = ',';
private static final char TRANSPORT_HOST_SEPARATOR_CHAR = ':';
private static final char HOST_PORT_SEPARATOR_CHAR = ':';
private static final char AT_CHAR = '@';
private static final char END_OF_URL_MARKER = '^';
enum URLParserState
{
QPID_URL_START,
ADDRESS_START,
PROPERTY_NAME,
PROPERTY_EQUALS,
PROPERTY_VALUE,
PROPERTY_SEPARATOR,
AT_CHAR,
TRANSPORT,
TRANSPORT_HOST_SEPARATOR,
HOST,
HOST_PORT_SEPARATOR,
PORT,
ADDRESS_END,
ADDRESS_SEPERATOR,
QPID_URL_END,
ERROR
}
//-- Constructors
private char[] _url;
private List<BrokerDetails> _brokerDetailList = new ArrayList<BrokerDetails>();
private String _error;
private int _index = 0;
private BrokerDetails _currentBroker;
private String _currentPropName;
private boolean _endOfURL = false;
private URLParserState _currentParserState;
public URLParser_0_10(String url) throws MalformedURLException
{
_url = (url + END_OF_URL_MARKER).toCharArray();
_endOfURL = false;
_currentParserState = URLParserState.QPID_URL_START;
URLParserState prevState = _currentParserState; // for error handling
try
{
while (_currentParserState != URLParserState.ERROR && _currentParserState != URLParserState.QPID_URL_END)
{
prevState = _currentParserState;
_currentParserState = next();
}
if (_currentParserState == URLParserState.ERROR)
{
_error =
"Invalid URL format [current_state = " + prevState + ", broker details parsed so far " + _currentBroker + " ] error at (" + _index + ") due to " + _error;
MalformedURLException ex;
ex = new MalformedURLException(_error);
throw ex;
}
}
catch (ArrayIndexOutOfBoundsException e)
{
_error = "Invalid URL format [current_state = " + prevState + ", broker details parsed so far " + _currentBroker + " ] error at (" + _index + ")";
MalformedURLException ex = new MalformedURLException(_error);
throw ex;
}
}
//-- interface QpidURL
public List<BrokerDetails> getAllBrokerDetails()
{
return _brokerDetailList;
}
public String getURL()
{
return new String(_url);
}
private URLParserState next()
{
switch (_currentParserState)
{
case QPID_URL_START:
return checkSequence(URL_START_SEQ, URLParserState.ADDRESS_START);
case ADDRESS_START:
return startAddress();
case PROPERTY_NAME:
return extractPropertyName();
case PROPERTY_EQUALS:
_index++; // skip the equal sign
return URLParserState.PROPERTY_VALUE;
case PROPERTY_VALUE:
return extractPropertyValue();
case PROPERTY_SEPARATOR:
_index++; // skip ","
return URLParserState.PROPERTY_NAME;
case AT_CHAR:
_index++; // skip the @ sign
return URLParserState.TRANSPORT;
case TRANSPORT:
return extractTransport();
case TRANSPORT_HOST_SEPARATOR:
_index++; // skip ":"
return URLParserState.HOST;
case HOST:
return extractHost();
case HOST_PORT_SEPARATOR:
_index++; // skip ":"
return URLParserState.PORT;
case PORT:
return extractPort();
case ADDRESS_END:
return endAddress();
case ADDRESS_SEPERATOR:
_index++; // skip ","
return URLParserState.ADDRESS_START;
default:
return URLParserState.ERROR;
}
}
private URLParserState checkSequence(char[] expected, URLParserState nextPart)
{
for (char expectedChar : expected)
{
if (expectedChar != _url[_index])
{
_error = "Excepted (" + expectedChar + ") at position " + _index + ", got (" + _url[_index] + ")";
return URLParserState.ERROR;
}
_index++;
}
return nextPart;
}
private URLParserState startAddress()
{
_currentBroker = new BrokerDetails();
for (int j = _index; j < _url.length; j++)
{
if (_url[j] == PROPERTY_EQUALS_CHAR)
{
return URLParserState.PROPERTY_NAME;
}
else if (_url[j] == ADDRESS_SEPERATOR_CHAR)
{
return URLParserState.TRANSPORT;
}
}
return URLParserState.TRANSPORT;
}
private URLParserState endAddress()
{
_brokerDetailList.add(_currentBroker);
if (_endOfURL)
{
return URLParserState.QPID_URL_END;
}
else
{
return URLParserState.ADDRESS_SEPERATOR;
}
}
private URLParserState extractPropertyName()
{
StringBuilder b = new StringBuilder();
char next = _url[_index];
while (next != PROPERTY_EQUALS_CHAR && next != AT_CHAR)
{
b.append(next);
next = _url[++_index];
}
_currentPropName = b.toString();
if (_currentPropName.trim().equals(""))
{
_error = "Property name cannot be empty";
return URLParserState.ERROR;
}
else if (next == PROPERTY_EQUALS_CHAR)
{
return URLParserState.PROPERTY_EQUALS;
}
else
{
return URLParserState.AT_CHAR;
}
}
private URLParserState extractPropertyValue()
{
StringBuilder b = new StringBuilder();
char next = _url[_index];
while (next != PROPERTY_SEPARATOR_CHAR && next != AT_CHAR)
{
b.append(next);
next = _url[++_index];
}
String propValue = b.toString();
if (propValue.trim().equals(""))
{
_error = "Property values cannot be empty";
return URLParserState.ERROR;
}
else
{
_currentBroker.setProperty(_currentPropName, propValue);
if (next == PROPERTY_SEPARATOR_CHAR)
{
return URLParserState.PROPERTY_SEPARATOR;
}
else
{
return URLParserState.AT_CHAR;
}
}
}
private URLParserState extractTransport()
{
String transport = buildUntil(TRANSPORT_HOST_SEPARATOR_CHAR);
if (transport.trim().equals(""))
{
_error = "Transport cannot be empty";
return URLParserState.ERROR;
}
else if (!(transport.trim().equals(BrokerDetails.PROTOCOL_TCP) || transport.trim()
.equals(BrokerDetails.PROTOCOL_TLS)))
{
_error = "Transport cannot be " + transport + " value must be tcp or tls";
return URLParserState.ERROR;
}
else
{
_currentBroker.setTransport(transport);
return URLParserState.TRANSPORT_HOST_SEPARATOR;
}
}
private URLParserState extractHost()
{
char nextSep = 'c';
String host;
URLParserState nextState;
for (int i = _index; i < _url.length; i++)
{
if (_url[i] == HOST_PORT_SEPARATOR_CHAR)
{
nextSep = HOST_PORT_SEPARATOR_CHAR;
break;
}
else if (_url[i] == ADDRESS_SEPERATOR_CHAR)
{
nextSep = ADDRESS_SEPERATOR_CHAR;
break;
}
}
if (nextSep == HOST_PORT_SEPARATOR_CHAR)
{
host = buildUntil(HOST_PORT_SEPARATOR_CHAR);
nextState = URLParserState.HOST_PORT_SEPARATOR;
}
else if (nextSep == ADDRESS_SEPERATOR_CHAR)
{
host = buildUntil(ADDRESS_SEPERATOR_CHAR);
nextState = URLParserState.ADDRESS_END;
}
else
{
host = buildUntil(END_OF_URL_MARKER);
nextState = URLParserState.ADDRESS_END;
_endOfURL = true;
}
if (host.trim().equals(""))
{
_error = "Host cannot be empty";
return URLParserState.ERROR;
}
else
{
_currentBroker.setHost(host);
return nextState;
}
}
private URLParserState extractPort()
{
StringBuilder b = new StringBuilder();
try
{
char next = _url[_index];
while (next != ADDRESS_SEPERATOR_CHAR && next != END_OF_URL_MARKER )
{
b.append(next);
next = _url[++_index];
}
}
catch (ArrayIndexOutOfBoundsException e)
{
_endOfURL = true;
}
String portStr = b.toString();
if (portStr.trim().equals(""))
{
_error = "Host cannot be empty";
return URLParserState.ERROR;
}
else
{
try
{
int port = Integer.parseInt(portStr);
_currentBroker.setPort(port);
if( _url[_index] == END_OF_URL_MARKER )
{
_endOfURL = true;
}
return URLParserState.ADDRESS_END;
}
catch (NumberFormatException e)
{
_error = "Illegal number for port";
return URLParserState.ERROR;
}
}
}
private String buildUntil(char c)
{
StringBuilder b = new StringBuilder();
char next = _url[_index];
while (next != c)
{
b.append(next);
next = _url[++_index];
}
return b.toString();
}
public static void main(String[] args)
{
String testurl = "qpid:password=pass;username=name@tcp:test1";
try
{
URLParser_0_10 impl = new URLParser_0_10(testurl);
for (BrokerDetails d : impl.getAllBrokerDetails())
{
System.out.println(d);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}