001package ball.upnp.ssdp; 002/*- 003 * ########################################################################## 004 * UPnP/SSDP Implementation Classes 005 * $Id: SSDPDiscoveryCache.java 5285 2020-02-05 04:23:21Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-upnp/trunk/src/main/java/ball/upnp/ssdp/SSDPDiscoveryCache.java $ 007 * %% 008 * Copyright (C) 2013 - 2020 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.URI; 024import java.util.Date; 025import java.util.TreeMap; 026import java.util.concurrent.ConcurrentSkipListMap; 027import java.util.regex.Pattern; 028import org.apache.http.Header; 029import org.apache.http.HttpHeaders; 030import org.apache.http.client.utils.DateUtils; 031 032import static ball.upnp.ssdp.SSDPMessage.MAX_AGE; 033import static ball.upnp.ssdp.SSDPMessage.SSDP_BYEBYE; 034import static java.util.Objects.requireNonNull; 035 036/** 037 * SSDP discovery cache implementation. 038 * 039 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 040 * @version $Revision: 5285 $ 041 */ 042public class SSDPDiscoveryCache 043 extends ConcurrentSkipListMap<URI,SSDPDiscoveryCache.Value> 044 implements SSDPDiscoveryThread.Listener { 045 private static final long serialVersionUID = -383765398333867476L; 046 047 /** 048 * Sole constructor. 049 */ 050 public SSDPDiscoveryCache() { 051 super(); 052 053 new Thread() { 054 { setDaemon(true); } 055 056 @Override 057 public void run() { 058 for (;;) { 059 try { 060 sleep(60 * 1000); 061 } catch (InterruptedException exception) { 062 } 063 064 values().removeIf(t -> now() > t.getExpiration()); 065 } 066 } 067 }.start(); 068 } 069 070 @Override 071 public void sendEvent(SSDPDiscoveryThread thread, SSDPMessage message) { 072 receiveEvent(thread, message); 073 } 074 075 @Override 076 public void receiveEvent(SSDPDiscoveryThread thread, SSDPMessage message) { 077 try { 078 long time = now(); 079 long expiration = 0; 080 Header header = message.getFirstHeader(HttpHeaders.CACHE_CONTROL); 081 082 if (header != null) { 083 CacheControlDirectiveMap map = 084 new CacheControlDirectiveMap(header.getValue()); 085 String value = map.get(MAX_AGE); 086 087 if (value != null) { 088 try { 089 header = message.getFirstHeader(HttpHeaders.DATE); 090 091 if (header != null) { 092 time = 093 DateUtils.parseDate(header.getValue()) 094 .getTime(); 095 } 096 } catch (Exception exception) { 097 } 098 099 expiration = time + (Long.decode(value) * 1000); 100 } 101 } else { 102 header = message.getFirstHeader(HttpHeaders.EXPIRES); 103 104 if (header != null) { 105 String value = header.getValue(); 106 Date date = DateUtils.parseDate(value); 107 108 if (date != null) { 109 expiration = date.getTime(); 110 } 111 } 112 } 113 114 if (expiration > time) { 115 put(message.getUSN(), new Value(message, expiration)); 116 } 117 } catch (Exception exception) { 118 } 119 120 if (message instanceof SSDPRequest) { 121 SSDPRequest request = (SSDPRequest) message; 122 String method = request.getRequestLine().getMethod(); 123 124 if (SSDPNotifyRequest.METHOD.equals(method)) { 125 Header header = message.getFirstHeader(SSDPMessage.NTS); 126 127 if (header != null && SSDP_BYEBYE.equals(header.getValue())) { 128 remove(request.getUSN()); 129 } 130 } 131 } 132 } 133 134 private long now() { return System.currentTimeMillis(); } 135 136 /** 137 * {@link SSDPDiscoveryCache} {@link java.util.Map} {@link Value} 138 * (expiration and {@link SSDPMessage}). 139 * 140 * {@bean.info} 141 */ 142 public class Value { 143 private final SSDPMessage message; 144 private long expiration = 0; 145 146 private Value(SSDPMessage message, long expiration) { 147 this.message = requireNonNull(message, "message"); 148 149 setExpiration(expiration); 150 } 151 152 public SSDPMessage getSSDPMessage() { return message; } 153 154 public long getExpiration() { return expiration; } 155 public void setExpiration(long expiration) { 156 if (expiration > 0) { 157 this.expiration = expiration; 158 } else { 159 throw new IllegalArgumentException("expiration=" + expiration); 160 } 161 } 162 163 @Override 164 public String toString() { return message.toString(); } 165 } 166 167 private class CacheControlDirectiveMap extends TreeMap<String,String> { 168 private static final long serialVersionUID = -7901522510091761313L; 169 170 public CacheControlDirectiveMap(String string) { 171 super(String.CASE_INSENSITIVE_ORDER); 172 173 for (String directive : string.trim().split(Pattern.quote(";"))) { 174 String[] pair = directive.split(Pattern.quote("="), 2); 175 176 put(pair[0].trim(), (pair.length > 1) ? pair[1].trim() : null); 177 } 178 } 179 } 180}