001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2016-2018 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jgrapes.io.util; 020 021import java.util.Optional; 022import org.jgrapes.core.Channel; 023import org.jgrapes.core.EventPipeline; 024import org.jgrapes.core.Manager; 025import org.jgrapes.core.Subchannel; 026import org.jgrapes.io.IOSubchannel; 027import org.jgrapes.io.IOSubchannel.DefaultIOSubchannel; 028 029/** 030 * Provides an I/O subchannel that is linked to another I/O subchannel. A 031 * typical use case for this class is a protocol converter. 032 * 033 * Protocol converters receive events related to an I/O resource from upstream, 034 * and while processing them usually generate new events to other components 035 * downstream (and vice versa). The events received are associated with a 036 * particular resource by the subchannel that is used to relay them. The 037 * association with the resource must be maintained for the newly generated 038 * events as well. It is, however, not possible to use the same subchannel for 039 * receiving from upstream and sending downstream because it wouldn't be 040 * possible to distinguish between e.g. an {@code Input} event from upstream to 041 * the converter and an {@code Input} event (conveying the converted data) from 042 * the converter to the downstream components. 043 * 044 * Therefore, the converter must provide and manage independent subchannels for 045 * the data streams on its downstream side with a one-to-one relationship to the 046 * upstream subchannels. The {@code LinkedIOSubchannel} class simplifies this 047 * task. It provides a new subchannel with a reference to an existing 048 * subchannel. This makes it easy to find the upstream subchannel for a 049 * given downstream ({@code LinkedIOSubchannel}) when processing response 050 * events. For finding the downstream {@code IOSubchannel} for a given upstream 051 * connection, instances associate themselves with the upstream channel using 052 * the converter component as key. This allows a subchannel to have several 053 * associated linked subchannels. 054 */ 055public class LinkedIOSubchannel extends DefaultIOSubchannel { 056 057 private final Manager hub; 058 private final IOSubchannel upstreamChannel; 059 private static ThreadLocal<Integer> linkedRemaining = new ThreadLocal<>(); 060 061 /** 062 * Creates a new {@code LinkedIOSubchannel} for a given main channel 063 * that links to the give I/O subchannel. Using this constructor 064 * is similar to invoking 065 * {@link #LinkedIOSubchannel(Manager, Channel, IOSubchannel, EventPipeline, boolean)} 066 * with {@code true} as last parameter. 067 * 068 * Because the newly created {@link LinkedIOSubchannel} is referenced by 069 * the upstream channel, it will life as long as the upstream channel, 070 * even if no further references exist. 071 * 072 * @param hub the component that manages this channel 073 * @param mainChannel the main channel 074 * @param upstreamChannel the upstream channel 075 * @param responsePipeline the response pipeline 076 */ 077 public LinkedIOSubchannel(Manager hub, Channel mainChannel, 078 IOSubchannel upstreamChannel, EventPipeline responsePipeline) { 079 this(hub, mainChannel, upstreamChannel, responsePipeline, true); 080 } 081 082 /** 083 * Creates a new {@code LinkedIOSubchannel} for a given main channel 084 * that links to a given I/O subchannel. 085 * 086 * Using this constructor with {@code false} as last parameter prevents the 087 * addition of the back link from the upstream channel to the downstream 088 * channel (see {@link #downstreamChannel(Manager, IOSubchannel)}). 089 * This can save some space if a converter component has some other 090 * means to maintain that information. Note that in this case a 091 * reference to the created {@link LinkedIOSubchannel} must be 092 * maintained, else it may be garbage collected. 093 * 094 * @param hub the converter component that manages this channel 095 * @param mainChannel the main channel 096 * @param upstreamChannel the upstream channel 097 * @param responsePipeline the response pipeline 098 * @param linkBack create the link from upstream to downstream 099 */ 100 public LinkedIOSubchannel(Manager hub, Channel mainChannel, 101 IOSubchannel upstreamChannel, EventPipeline responsePipeline, 102 boolean linkBack) { 103 super(mainChannel, responsePipeline); 104 this.hub = hub; 105 this.upstreamChannel = upstreamChannel; 106 if (linkBack) { 107 upstreamChannel.setAssociated( 108 new KeyWrapper(hub), this); 109 } 110 } 111 112 /** 113 * Removes the association between the upstream channel and this 114 * channel. Should only be called if this channel is no longer used. 115 * 116 * @param hub the converter component that manages this channel 117 */ 118 public void unlink(Manager hub) { 119 upstreamChannel.setAssociated(new KeyWrapper(hub), null); 120 } 121 122 /** 123 * Returns the component that manages this channel. 124 * 125 * @return the component that manages this channel 126 */ 127 public Manager hub() { 128 return hub; 129 } 130 131 /** 132 * Returns the upstream channel. 133 * 134 * @return the upstream channel 135 */ 136 public IOSubchannel upstreamChannel() { 137 return upstreamChannel; 138 } 139 140 /** 141 * Delegates the invocation to the upstream channel 142 * if no associated data is found for this channel. 143 * 144 * @param <V> the value type 145 * @param by the associator 146 * @param type the type 147 * @return the optional 148 */ 149 @Override 150 @SuppressWarnings("PMD.ShortVariable") 151 public <V> Optional<V> associated(Object by, Class<V> type) { 152 Optional<V> result = super.associated(by, type); 153 if (!result.isPresent()) { 154 IOSubchannel upstream = upstreamChannel(); 155 if (upstream != null) { 156 return upstream.associated(by, type); 157 } 158 } 159 return result; 160 } 161 162 /** 163 * The {@link #toString()} method of {@link LinkedIOSubchannel}s 164 * shows the channel together with the upstream channel that it 165 * is linked to. If there are several levels of upstream links, 166 * this can become a very long sequence. 167 * 168 * This method creates the representation of the linked upstream 169 * channel (an arrow followed by the representation of the channel), 170 * but only up to one level. If more levels exist, it returns 171 * an arrow followed by an ellipses. This method is used by 172 * {@link LinkedIOSubchannel#toString()}. Other implementations 173 * of {@link IOSubchannel} should use this method in their 174 * {@link Object#toString()} method as well to keep the result 175 * consistent. 176 * 177 * @param upstream the upstream channel 178 * @return the string 179 */ 180 public static String upstreamToString(Channel upstream) { 181 if (upstream == null) { 182 linkedRemaining.set(null); 183 return ""; 184 } 185 if (linkedRemaining.get() == null) { 186 linkedRemaining.set(1); 187 } 188 if (linkedRemaining.get() <= 0) { 189 linkedRemaining.set(null); 190 return "↔…"; 191 } 192 linkedRemaining.set(linkedRemaining.get() - 1); 193 194 // Build continuation. 195 StringBuilder builder = new StringBuilder(); 196 builder.append('↔') 197 .append(Channel.toString(upstream)); 198 linkedRemaining.set(null); 199 return builder.toString(); 200 201 } 202 203 /* 204 * (non-Javadoc) 205 * 206 * @see java.lang.Object#toString() 207 */ 208 @Override 209 public String toString() { 210 StringBuilder builder = new StringBuilder(); 211 builder.append(Subchannel.toString(this)); 212 if (upstreamChannel != null) { 213 builder.append(upstreamToString(upstreamChannel)); 214 } 215 return builder.toString(); 216 } 217 218 /** 219 * Returns the linked downstream channel that has been created for the 220 * given component and (upstream) subchannel. If more than one linked 221 * subchannel has been created for a given component and subchannel, 222 * the linked subchannel created last is returned. 223 * 224 * @param hub the component that manages this channel 225 * @param upstreamChannel the (upstream) channel 226 * @return the linked downstream subchannel created for the 227 * given component and (upstream) subchannel if it exists 228 */ 229 public static Optional<? extends LinkedIOSubchannel> downstreamChannel( 230 Manager hub, IOSubchannel upstreamChannel) { 231 return upstreamChannel.associated( 232 new KeyWrapper(hub), LinkedIOSubchannel.class); 233 } 234 235 /** 236 * Like {@link #downstreamChannel(Manager, IOSubchannel)}, but 237 * with the return value of the specified type. 238 * 239 * @param <T> the generic type 240 * @param hub the component that manages this channel 241 * @param upstreamChannel the (upstream) channel 242 * @param clazz the type of the returned value 243 * @return the linked downstream subchannel created for the 244 * given component and (upstream) subchannel if it exists 245 */ 246 public static <T extends LinkedIOSubchannel> Optional<T> downstreamChannel( 247 Manager hub, IOSubchannel upstreamChannel, Class<T> clazz) { 248 return upstreamChannel.associated( 249 new KeyWrapper(hub), clazz); 250 } 251 252 /** 253 * Artificial key. 254 */ 255 private static class KeyWrapper { 256 257 private final Manager hub; 258 259 /** 260 * @param hub 261 */ 262 public KeyWrapper(Manager hub) { 263 super(); 264 this.hub = hub; 265 } 266 267 /* 268 * (non-Javadoc) 269 * 270 * @see java.lang.Object#hashCode() 271 */ 272 @Override 273 @SuppressWarnings("PMD.DataflowAnomalyAnalysis") 274 public int hashCode() { 275 @SuppressWarnings("PMD.AvoidFinalLocalVariable") 276 final int prime = 31; 277 int result = 1; 278 result = prime * result + ((hub == null) ? 0 279 : hub.hashCode()); 280 return result; 281 } 282 283 /* 284 * (non-Javadoc) 285 * 286 * @see java.lang.Object#equals(java.lang.Object) 287 */ 288 @Override 289 public boolean equals(Object obj) { 290 if (this == obj) { 291 return true; 292 } 293 if (obj == null) { 294 return false; 295 } 296 if (getClass() != obj.getClass()) { 297 return false; 298 } 299 KeyWrapper other = (KeyWrapper) obj; 300 if (hub == null) { 301 if (other.hub != null) { 302 return false; 303 } 304 } else if (!hub.equals(other.hub)) { 305 return false; 306 } 307 return true; 308 } 309 } 310}