1 package org.olsr.v1.info.proxy; 2 3 import java.io.IOException; 4 import java.net.HttpURLConnection; 5 import java.net.InetSocketAddress; 6 import java.net.Socket; 7 import java.net.SocketAddress; 8 import java.util.List; 9 import java.util.Random; 10 import java.util.concurrent.CopyOnWriteArrayList; 11 12 import org.olsr.v1.info.proxy.api.InfoResult; 13 import org.olsr.v1.info.proxy.api.OlsrdInfoProxy; 14 import org.osgi.service.component.annotations.Activate; 15 import org.osgi.service.component.annotations.Component; 16 import org.osgi.service.component.annotations.Deactivate; 17 import org.osgi.service.component.annotations.Modified; 18 import org.osgi.service.metatype.annotations.Designate; 19 20 @Component 21 @Designate(ocd = Config.class) 22 public class OlsrdInfoProxyImpl implements OlsrdInfoProxy { 23 /* 24 * Config 25 */ 26 27 private final Object configLock = new Object(); 28 private Config config = null; 29 InetSocketAddress addressTxtInfo = 30 new InetSocketAddress(Config.ADDRESS_DEFAULT, Config.PORT_TXTINFO_DEFAULT); 31 InetSocketAddress addressJsonInfo = 32 new InetSocketAddress(Config.ADDRESS_DEFAULT, Config.PORT_JSONINFO_DEFAULT); 33 34 CopyOnWriteArrayList<Integer> usedPorts = new CopyOnWriteArrayList<>(); 35 36 private final Random random = new Random(); 37 38 /* MUST be called in a synchronized (configLock) block */ setupConfig(final Config config)39 private Config setupConfig(final Config config) { 40 final Config preConfig = this.config; 41 this.config = config; 42 43 final InetSocketAddress sa = new InetSocketAddress(this.config.address(), 0); 44 if (!sa.isUnresolved()) { 45 this.addressTxtInfo = new InetSocketAddress(this.config.address(), this.config.portTxtInfo()); 46 this.addressJsonInfo = new InetSocketAddress(this.config.address(), this.config.portJsonInfo()); 47 } 48 49 return preConfig; 50 } 51 52 /* 53 * Lifecycle 54 */ 55 56 @Activate activate(final Config config)57 void activate(final Config config) { 58 synchronized (this.configLock) { 59 this.setupConfig(config); 60 this.usedPorts.clear(); 61 this.usedPorts.add(Integer.valueOf(this.config.localPort())); 62 } 63 } 64 65 @Deactivate deactivate()66 void deactivate() { 67 /* nothing to do */ 68 } 69 70 @Modified modified(final Config config)71 void modified(final Config config) { 72 Integer preLocalPort; 73 Integer localPort; 74 synchronized (this.configLock) { 75 preLocalPort = Integer.valueOf(this.config.localPort()); 76 this.setupConfig(config); 77 localPort = Integer.valueOf(this.config.localPort()); 78 } 79 80 if (!localPort.equals(preLocalPort)) { 81 this.usedPorts.remove(preLocalPort); 82 this.usedPorts.add(0, localPort); 83 } 84 } 85 86 /* 87 * Helpers 88 */ 89 getSocket(final int socketTimeout)90 Socket getSocket(final int socketTimeout) { 91 Socket socket = null; 92 93 for (final Integer port : this.usedPorts) { 94 final SocketAddress bindpoint = new InetSocketAddress(port.intValue()); 95 96 socket = new Socket(); 97 try { 98 socket.setSoTimeout(socketTimeout); 99 socket.setReuseAddress(true); 100 socket.bind(bindpoint); 101 } 102 catch (final IOException e) { 103 /* skip to next port */ 104 socket = null; 105 } 106 } 107 108 if (socket == null) { 109 socket = new Socket(); 110 try { 111 socket.setSoTimeout(socketTimeout); 112 socket.setReuseAddress(true); 113 socket.bind(null); 114 final Integer localPort = Integer.valueOf(socket.getLocalPort()); 115 this.usedPorts.addIfAbsent(localPort); 116 } 117 catch (final IOException e) { 118 /* swallow & can't be covered in a test */ 119 } 120 } 121 122 assert (socket != null); 123 return socket; 124 } 125 sendInternal(final String command, final List<String> txtInfo, final StringBuilder jsonInfo)126 public int sendInternal(final String command, final List<String> txtInfo, final StringBuilder jsonInfo) 127 throws IOException { 128 assert (!((txtInfo == null) && (jsonInfo == null))); 129 assert (!((txtInfo != null) && (jsonInfo != null))); 130 131 if ((command == null) // 132 || command.isEmpty()) { 133 throw new IOException("No command specified"); 134 } 135 136 int socketTimeout; 137 int connectionTimeout; 138 InetSocketAddress address; 139 int randomSleep; 140 int retries; 141 synchronized (this.configLock) { 142 socketTimeout = this.config.socketTimeout(); 143 connectionTimeout = this.config.connectionTimeout(); 144 address = (txtInfo != null) ? this.addressTxtInfo : this.addressJsonInfo; 145 randomSleep = this.config.randomSleep(); 146 retries = this.config.connectionRetries(); 147 } 148 149 int retry = 0; 150 while (retry <= retries) { 151 try (Socket socket = this.getSocket(socketTimeout)) { 152 final int r = 153 OlsrdInfoReader.send(socket, command, connectionTimeout, socketTimeout, address, txtInfo, jsonInfo); 154 if (r != HttpURLConnection.HTTP_UNAVAILABLE) { 155 return r; 156 } 157 } 158 159 try { 160 Thread.sleep(Math.max(Config.RANDOM_SLEEP_MIN, this.random.nextInt(randomSleep))); 161 } 162 catch (final InterruptedException e) { 163 /* swallow & can't be covered in a test */ 164 retry = retries; 165 } 166 167 retry++; 168 } 169 170 return HttpURLConnection.HTTP_UNAVAILABLE; 171 } 172 173 /* 174 * OlsrdInfoProxy 175 */ 176 177 @Override getTxtInfo(final String command)178 public InfoResult getTxtInfo(final String command) throws IOException { 179 final InfoResult result = new InfoResult(); 180 result.status = this.sendInternal(command, result.output, null); 181 return result; 182 } 183 184 @Override getJsonInfo(final String command)185 public InfoResult getJsonInfo(final String command) throws IOException { 186 final InfoResult result = new InfoResult(); 187 final StringBuilder sb = new StringBuilder(); 188 result.status = this.sendInternal(command, null, sb); 189 result.output.add(sb.toString()); 190 return result; 191 } 192 }