Java tutorial
/* * Copyright (C) 2013 Burton Alexander * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ package asia.stampy.common.parsing; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import asia.stampy.client.message.abort.AbortMessage; import asia.stampy.client.message.ack.AckMessage; import asia.stampy.client.message.begin.BeginMessage; import asia.stampy.client.message.commit.CommitMessage; import asia.stampy.client.message.connect.ConnectMessage; import asia.stampy.client.message.disconnect.DisconnectMessage; import asia.stampy.client.message.nack.NackMessage; import asia.stampy.client.message.send.SendMessage; import asia.stampy.client.message.stomp.StompMessage; import asia.stampy.client.message.subscribe.SubscribeMessage; import asia.stampy.client.message.unsubscribe.UnsubscribeMessage; import asia.stampy.common.StampyLibrary; import asia.stampy.common.message.AbstractBodyMessage; import asia.stampy.common.message.AbstractBodyMessageHeader; import asia.stampy.common.message.StampyMessage; import asia.stampy.common.message.StompMessageType; import asia.stampy.common.serialization.SerializationUtils; import asia.stampy.server.message.connected.ConnectedMessage; import asia.stampy.server.message.error.ErrorMessage; import asia.stampy.server.message.message.MessageMessage; import asia.stampy.server.message.receipt.ReceiptMessage; /** * This class parses STOMP messages into {@link StampyMessage}s. */ @StampyLibrary(libraryName = "stampy-core") public class StompMessageParser { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); /** The Constant EOM. */ public static final String EOM = "\000"; /** * Parses the message. * * @param <MSG> * the generic type * @param stompMessage * the stomp message * @return the msg * @throws UnparseableException * the unparseable exception */ public <MSG extends StampyMessage<?>> MSG parseMessage(String stompMessage) throws UnparseableException { BufferedReader reader = null; try { reader = new BufferedReader(new StringReader(stompMessage)); String messageType; while ((messageType = reader.readLine()).length() < 2) ; //skip emtpy lines... //TODO: should check for key words like CONNECT, SEND,... StompMessageType type = StompMessageType.valueOf(messageType); List<String> headers = new ArrayList<String>(); String hdr = reader.readLine(); while (StringUtils.isNotEmpty(hdr)) { headers.add(hdr); hdr = reader.readLine(); } String body = reader.readLine(); body = body == null || body.equals(EOM) ? null : fillBody(body, reader); MSG msg = createStampyMessage(type, headers); if (!StringUtils.isEmpty(body) && msg instanceof AbstractBodyMessage<?>) { AbstractBodyMessage<?> abm = (AbstractBodyMessage<?>) msg; abm.setBody(isText(headers) ? body : convertToObject(body, abm.getHeader().getContentType())); } return msg; } catch (Exception e) { throw new UnparseableException("The message supplied cannot be parsed as a STOMP message", stompMessage, e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { log.warn("Could not close reader", e); } } } } /** * Converts the specified string to an object based upon the specified content * type. Only base64 encoding is supported for Java objects. * * @param body * the body * @param contentType * the content type * @return the object * @throws IllegalObjectException * the illegal object exception * @throws ClassNotFoundException * the class not found exception * @throws IOException * Signals that an I/O exception has occurred. */ protected Object convertToObject(String body, String contentType) throws IllegalObjectException, ClassNotFoundException, IOException { if (!AbstractBodyMessage.JAVA_BASE64_MIME_TYPE.equals(contentType)) { throw new NotImplementedException( "Subclass this class and override convertToObject to enable conversion using mime type " + contentType); } Object o = SerializationUtils.deserializeBase64(body); illegalObjectCheck(o); return o; } /** * Blank implementation; override to add any object checking logic. * * @param o * the o * @throws IllegalObjectException * the illegal object exception */ protected void illegalObjectCheck(Object o) throws IllegalObjectException { } /** * Checks if is text. * * @param headers * the headers * @return true, if is text */ protected boolean isText(List<String> headers) { boolean text = false; boolean content = false; for (String hdr : headers) { if (hdr.contains(AbstractBodyMessageHeader.CONTENT_TYPE)) { content = true; text = hdr.contains("text/"); } } return !content || (content && text); } /** * Creates the stampy message. * * @param <MSG> * the generic type * @param type * the type * @param headers * the headers * @return the msg * @throws UnparseableException * the unparseable exception */ @SuppressWarnings("unchecked") protected <MSG extends StampyMessage<?>> MSG createStampyMessage(StompMessageType type, List<String> headers) throws UnparseableException { MSG message = null; switch (type) { case ABORT: message = (MSG) new AbortMessage(); break; case ACK: message = (MSG) new AckMessage(); break; case BEGIN: message = (MSG) new BeginMessage(); break; case COMMIT: message = (MSG) new CommitMessage(); break; case CONNECT: message = (MSG) new ConnectMessage(); break; case CONNECTED: message = (MSG) new ConnectedMessage(); break; case DISCONNECT: message = (MSG) new DisconnectMessage(); break; case ERROR: ErrorMessage error = new ErrorMessage(); message = (MSG) error; break; case MESSAGE: MessageMessage mm = new MessageMessage(); message = (MSG) mm; break; case NACK: message = (MSG) new NackMessage(); break; case RECEIPT: message = (MSG) new ReceiptMessage(); break; case SEND: SendMessage send = new SendMessage(); message = (MSG) send; break; case STOMP: message = (MSG) new StompMessage(); break; case SUBSCRIBE: message = (MSG) new SubscribeMessage(); break; case UNSUBSCRIBE: message = (MSG) new UnsubscribeMessage(); break; default: break; } message.getHeader(); addHeaders(message, headers); return message; } private <MSG extends StampyMessage<?>> void addHeaders(MSG message, List<String> headers) throws UnparseableException { for (String header : headers) { StringTokenizer st = new StringTokenizer(header, ":"); if (st.countTokens() < 1) { log.error("Cannot parse STOMP header {}", header); throw new UnparseableException("Cannot parse STOMP header " + header); } String value = ""; String key = st.nextToken(); if (st.hasMoreTokens()) { value = header.substring(key.length() + 1); } message.getHeader().addHeader(key, value); } } /** * Fills the body of the STOMP message. * * @param body * the body * @param reader * the reader * @return the string * @throws IOException * Signals that an I/O exception has occurred. */ protected String fillBody(String body, BufferedReader reader) throws IOException { StringBuilder builder = new StringBuilder(trimEOM(body)); String s = reader.readLine(); while (s != null) { builder.append(trimEOM(s)); s = reader.readLine(); } return builder.toString(); } /** * Trims the terminating byte. * * @param s * the s * @return the string */ protected String trimEOM(String s) { String trimmed = s; if (s.contains(EOM)) { int idx = s.indexOf(EOM); trimmed = s.substring(0, idx); } return trimmed; } }