001package ball.upnp.ssdp; 002/*- 003 * ########################################################################## 004 * UPnP/SSDP Implementation Classes 005 * %% 006 * Copyright (C) 2013 - 2022 Allen D. Ball 007 * %% 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 * ########################################################################## 020 */ 021import java.net.DatagramPacket; 022import java.net.InetSocketAddress; 023import java.net.SocketAddress; 024import java.net.URI; 025import java.util.List; 026import java.util.Objects; 027import java.util.function.Function; 028import java.util.stream.Stream; 029import org.apache.hc.core5.http.ParseException; 030import org.apache.hc.core5.http.message.BasicHttpResponse; 031import org.apache.hc.core5.http.message.BasicLineParser; 032import org.apache.hc.core5.http.message.StatusLine; 033import org.apache.hc.core5.util.CharArrayBuffer; 034 035import static java.util.stream.Collectors.joining; 036import static org.apache.commons.lang3.StringUtils.EMPTY; 037import static org.apache.commons.lang3.StringUtils.SPACE; 038 039/** 040 * SSDP {@link org.apache.hc.core5.http.HttpResponse} implementation. 041 * 042 * {@bean.info} 043 * 044 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 045 */ 046public class SSDPResponse extends BasicHttpResponse implements SSDPMessage { 047 private static final long serialVersionUID = -5163694432470542459L; 048 049 /** 050 * Method to parse a {@link SSDPResponse} from a {@link DatagramPacket}. 051 * 052 * @param packet The {@link DatagramPacket}. 053 * 054 * @return A new {@link SSDPResponse}. 055 * 056 * @throws ParseException If the {@link DatagramPacket} cannot be 057 * parsed. 058 */ 059 public static SSDPResponse from(DatagramPacket packet) throws ParseException { 060 List<CharArrayBuffer> list = SSDPMessage.parse(packet); 061 StatusLine line = BasicLineParser.INSTANCE.parseStatusLine(list.remove(0)); 062 SSDPResponse response = new SSDPResponse(line.getStatusCode(), line.getReasonPhrase()); 063 064 for (CharArrayBuffer buffer : list) { 065 response.addHeader(BasicLineParser.INSTANCE.parseHeader(buffer)); 066 } 067 068 return response; 069 } 070 071 /** @serial */ private SocketAddress address = null; 072 /** @serial */ private long timestamp = System.currentTimeMillis(); 073 /** @serial */ private Long expiration = null; 074 075 /** 076 * Sole constructor. 077 * 078 * @param code The {@link SSDPRequest} {@code code}. 079 * @param reason The {@link SSDPRequest} reason. 080 */ 081 protected SSDPResponse(int code, String reason) { 082 super(code, reason); 083 } 084 085 /** 086 * Method to get the {@link SocketAddress} from the 087 * {@link DatagramPacket} if {@link.this} {@link SSDPResponse} was 088 * parsed from a packet. 089 * 090 * @return The {@link SocketAddress}. 091 */ 092 public SocketAddress getSocketAddress() { return address; } 093 094 public String getStatusLine() { 095 String string = 096 Stream.of(getVersion(), getCode(), getReasonPhrase()) 097 .filter(Objects::nonNull) 098 .map(Object::toString) 099 .collect(joining(SPACE)); 100 101 return string; 102 } 103 104 /** 105 * {@link String} fluent header setter. 106 * 107 * @param name The header name. 108 * @param value The header value. 109 * 110 * @return {@link.this} 111 */ 112 public SSDPResponse header(String name, String value) { 113 setHeader(name, value); 114 115 return this; 116 } 117 118 /** 119 * {@link SocketAddress} fluent header setter. 120 * 121 * @param name The header name. 122 * @param value The header value. 123 * 124 * @return {@link.this} 125 */ 126 public SSDPResponse header(String name, SocketAddress value) { 127 return header(name, (InetSocketAddress) value); 128 } 129 130 /** 131 * {@link InetSocketAddress} fluent header setter. 132 * 133 * @param name The header name. 134 * @param value The header value. 135 * 136 * @return {@link.this} 137 */ 138 public SSDPResponse header(String name, InetSocketAddress value) { 139 return header(name, t -> String.format("%s:%d", value.getAddress().getHostAddress(), value.getPort()), value); 140 } 141 142 /** 143 * {@link Number} fluent header setter. 144 * 145 * @param name The header name. 146 * @param value The header value. 147 * 148 * @return {@link.this} 149 */ 150 public SSDPResponse header(String name, Number value) { 151 return header(name, Number::toString, value); 152 } 153 154 /** 155 * {@link URI} fluent header setter. 156 * 157 * @param name The header name. 158 * @param value The header value. 159 * 160 * @return {@link.this} 161 */ 162 public SSDPResponse header(String name, URI value) { 163 return header(name, URI::toASCIIString, value); 164 } 165 166 /** 167 * Fluent header setter. 168 * 169 * @param <T> The target type. 170 * @param name The header name. 171 * @param value The header value. 172 * 173 * @return {@link.this} 174 */ 175 public <T> SSDPResponse header(String name, Function<T,String> function, T value) { 176 setHeader(name, (value != null) ? function.apply(value) : null); 177 178 return this; 179 } 180 181 @Override 182 public long getExpiration() { 183 if (expiration == null) { 184 expiration = SSDPMessage.getExpiration(this, timestamp); 185 } 186 187 return expiration; 188 } 189 190 @Override 191 public String toString() { 192 String string = 193 Stream.concat(Stream.of(getStatusLine()), Stream.of(getHeaders())) 194 .filter(Objects::nonNull) 195 .map(Objects::toString) 196 .collect(joining(EOL, EMPTY, EOM)); 197 198 return string; 199 } 200}