001package ball.upnp.ssdp;
002/*-
003 * ##########################################################################
004 * UPnP/SSDP Implementation Classes
005 * $Id: SSDPResponse.java 7215 2021-01-03 18:39:51Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-upnp/trunk/src/main/java/ball/upnp/ssdp/SSDPResponse.java $
007 * %%
008 * Copyright (C) 2013 - 2021 Allen D. Ball
009 * %%
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 *
014 *      http://www.apache.org/licenses/LICENSE-2.0
015 *
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 * ##########################################################################
022 */
023import java.net.DatagramPacket;
024import java.net.InetSocketAddress;
025import java.net.SocketAddress;
026import java.net.URI;
027import java.util.List;
028import java.util.Objects;
029import java.util.function.Function;
030import java.util.regex.Pattern;
031import java.util.stream.Stream;
032import org.apache.http.Header;
033import org.apache.http.HttpVersion;
034import org.apache.http.message.BasicHttpResponse;
035
036import static java.nio.charset.StandardCharsets.UTF_8;
037import static java.util.stream.Collectors.joining;
038import static org.apache.commons.lang3.StringUtils.EMPTY;
039import static org.apache.http.message.BasicLineParser.INSTANCE;
040import static org.apache.http.message.BasicLineParser.parseHeader;
041import static org.apache.http.message.BasicLineParser.parseStatusLine;
042
043/**
044 * SSDP {@link org.apache.http.HttpResponse} implementation.
045 *
046 * {@bean.info}
047 *
048 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
049 * @version $Revision: 7215 $
050 */
051public class SSDPResponse extends BasicHttpResponse implements SSDPMessage {
052
053    /**
054     * Method to parse a {@link SSDPResponse} from a
055     * {@link DatagramPacket}.
056     *
057     * @param   packet          The {@link DatagramPacket}.
058     *
059     * @return  A new {@link SSDPResponse}.
060     */
061    public static SSDPResponse from(DatagramPacket packet) {
062        return new SSDPResponse(packet);
063    }
064
065    private SocketAddress address = null;
066    private long timestamp = System.currentTimeMillis();
067    private Long expiration = null;
068
069    /**
070     * Sole non-private constructor.
071     *
072     * @param   code            The {@link SSDPRequest} {@code code}.
073     * @param   reason          The {@link SSDPRequest} reason.
074     */
075    protected SSDPResponse(int code, String reason) {
076        super(HttpVersion.HTTP_1_1, code, reason);
077    }
078
079    private SSDPResponse(DatagramPacket packet) {
080        this(packet, SSDPMessage.parse(packet));
081    }
082
083    private SSDPResponse(DatagramPacket packet, List<String> lines) {
084        super(parseStatusLine(lines.remove(0), INSTANCE));
085
086        lines.stream().forEach(t -> addHeader(parseHeader(t, INSTANCE)));
087
088        address = packet.getSocketAddress();
089    }
090
091    /**
092     * Method to get the {@link SocketAddress} from the
093     * {@link DatagramPacket} if {@link.this} {@link SSDPResponse} was
094     * parsed from a packet.
095     *
096     * @return  The {@link SocketAddress}.
097     */
098    public SocketAddress getSocketAddress() { return address; }
099
100    /**
101     * {@link String} fluent header setter.
102     *
103     * @param   name            The header name.
104     * @param   value           The header value.
105     *
106     * @return  {@link.this}
107     */
108    public SSDPResponse header(String name, String value) {
109        setHeader(name, value);
110
111        return this;
112    }
113
114    /**
115     * {@link SocketAddress} fluent header setter.
116     *
117     * @param   name            The header name.
118     * @param   value           The header value.
119     *
120     * @return  {@link.this}
121     */
122    public SSDPResponse header(String name, SocketAddress value) {
123        return header(name, (InetSocketAddress) value);
124    }
125
126    /**
127     * {@link InetSocketAddress} fluent header setter.
128     *
129     * @param   name            The header name.
130     * @param   value           The header value.
131     *
132     * @return  {@link.this}
133     */
134    public SSDPResponse header(String name, InetSocketAddress value) {
135        return header(name,
136                      t -> String.format("%s:%d",
137                                         value.getAddress().getHostAddress(),
138                                         value.getPort()),
139                      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,
176                                   Function<T,String> function, T value) {
177        setHeader(name, (value != null) ? function.apply(value) : null);
178
179        return this;
180    }
181
182    @Override
183    public long getExpiration() {
184        if (expiration == null) {
185            expiration = SSDPMessage.getExpiration(this, timestamp);
186        }
187
188        return expiration;
189    }
190
191    @Override
192    public String toString() {
193        String string =
194            Stream.concat(Stream.of(getStatusLine()),
195                          Stream.of(getAllHeaders()))
196            .filter(Objects::nonNull)
197            .map(Objects::toString)
198            .collect(joining(EOL, EMPTY, EOM));
199
200        return string;
201    }
202}