View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.model.message;
17  
18  import org.fourthline.cling.model.message.header.ContentTypeHeader;
19  import org.fourthline.cling.model.message.header.UpnpHeader;
20  
21  import java.io.UnsupportedEncodingException;
22  
23  /**
24   * A non-streaming message, the interface between the transport layer and the protocols.
25   * <p>
26   * Defaults to UDA version 1.0 and a string body type. Message content is not streamed,
27   * it is always read into memory and transported as a string or bytes message body.
28   * </p>
29   * <p>
30   * Subtypes of this class typically implement the integrity rules for individual UPnP
31   * messages, for example, what headers a particular message requires.
32   * </p>
33   * <p>
34   * Messages are not thread-safe.
35   * </p>
36   * 
37   * @author Christian Bauer
38   */
39  public abstract class UpnpMessage<O extends UpnpOperation> {
40  
41      public static enum BodyType {
42          STRING, BYTES
43      }
44  
45      private int udaMajorVersion = 1;
46      private int udaMinorVersion = 0;
47  
48      private O operation;
49      private UpnpHeaders headers = new UpnpHeaders();
50      private Object body;
51      private BodyType bodyType = BodyType.STRING;
52  
53      protected UpnpMessage(UpnpMessage<O> source) {
54          this.operation = source.getOperation();
55          this.headers = source.getHeaders();
56          this.body = source.getBody();
57          this.bodyType = source.getBodyType();
58          this.udaMajorVersion = source.getUdaMajorVersion();
59          this.udaMinorVersion = source.getUdaMinorVersion();
60      }
61  
62      protected UpnpMessage(O operation) {
63          this.operation = operation;
64      }
65  
66      protected UpnpMessage(O operation, BodyType bodyType, Object body) {
67          this.operation = operation;
68          this.bodyType = bodyType;
69          this.body = body;
70      }
71  
72      public int getUdaMajorVersion() {
73          return udaMajorVersion;
74      }
75  
76      public void setUdaMajorVersion(int udaMajorVersion) {
77          this.udaMajorVersion = udaMajorVersion;
78      }
79  
80      public int getUdaMinorVersion() {
81          return udaMinorVersion;
82      }
83  
84      public void setUdaMinorVersion(int udaMinorVersion) {
85          this.udaMinorVersion = udaMinorVersion;
86      }
87  
88      public UpnpHeaders getHeaders() {
89          return headers;
90      }
91  
92      public void setHeaders(UpnpHeaders headers) {
93          this.headers = headers;
94      }
95  
96      public Object getBody() {
97          return body;
98      }
99  
100     public void setBody(String string) {
101         this.bodyType = BodyType.STRING;
102         this.body = string;
103     }
104 
105     public void setBody(BodyType bodyType, Object body) {
106         this.bodyType = bodyType;
107         this.body = body;
108     }
109 
110     public void setBodyCharacters(byte[] characterData) throws UnsupportedEncodingException {
111         setBody(
112                 UpnpMessage.BodyType.STRING,
113                 new String(
114                         characterData,
115                         getContentTypeCharset() != null
116                                 ? getContentTypeCharset()
117                                 : "UTF-8"
118                 )
119         );
120     }
121 
122     public boolean hasBody() {
123         return getBody() != null;
124     }
125 
126     public BodyType getBodyType() {
127         return bodyType;
128     }
129 
130     public void setBodyType(BodyType bodyType) {
131         this.bodyType = bodyType;
132     }
133 
134     public String getBodyString() {
135         try {
136                 if(!hasBody()) {
137                     return null;
138                 }
139                 if(getBodyType().equals(BodyType.STRING)) {
140                     String body = ((String) getBody());
141                     if(body.charAt(0) == '\ufeff') { /* utf8 BOM */
142                         body = body.substring(1);
143                     }
144                     return body;
145                 } else {
146                     return new String((byte[]) getBody(), "UTF-8");
147                 }
148         } catch (Exception ex) {
149             throw new RuntimeException(ex);
150         }
151     }
152 
153     public byte[] getBodyBytes() {
154         try {
155             if(!hasBody()) {
156                 return null;
157             }
158             if(getBodyType().equals(BodyType.STRING)) {
159                 return getBodyString().getBytes();
160             } else {
161                 return (byte[]) getBody();
162             }
163         } catch (Exception ex) {
164             throw new RuntimeException(ex);
165         }
166     }
167 
168     public O getOperation() {
169         return operation;
170     }
171 
172     public boolean isContentTypeMissingOrText() {
173         ContentTypeHeader contentTypeHeader = getContentTypeHeader();
174         // This is against the HTTP specification: If there is no content type we MAY assume that
175         // the entity body is bytes. However, to support broken UPnP devices which also violate the
176         // UPnP spec and do not send any content type at all, we need to assume no content type
177         // means a textual entity body is available.
178         if (contentTypeHeader == null) return true;
179         if (contentTypeHeader.isText()) return true;
180         // Only if there was any content-type header and none was text
181         return false;
182     }
183 
184     public ContentTypeHeader getContentTypeHeader() {
185         return getHeaders().getFirstHeader(UpnpHeader.Type.CONTENT_TYPE, ContentTypeHeader.class);
186     }
187 
188     public boolean isContentTypeText() {
189         ContentTypeHeader ct = getContentTypeHeader();
190         return ct != null && ct.isText();
191     }
192 
193     public boolean isContentTypeTextUDA() {
194         ContentTypeHeader ct = getContentTypeHeader();
195         return ct != null && ct.isUDACompliantXML();
196     }
197 
198     public String getContentTypeCharset() {
199         ContentTypeHeader ct = getContentTypeHeader();
200         return ct != null ? ct.getValue().getParameters().get("charset") : null;
201     }
202 
203     public boolean hasHostHeader() {
204         return getHeaders().getFirstHeader(UpnpHeader.Type.HOST) != null;
205     }
206 
207     public boolean isBodyNonEmptyString() {
208         return hasBody()
209             && getBodyType().equals(UpnpMessage.BodyType.STRING)
210             && getBodyString().length() > 0;
211     }
212 
213     @Override
214     public String toString() {
215         return "(" + getClass().getSimpleName() + ") " + getOperation().toString();
216     }
217 }