View Javadoc

1   package org.jscsi.target.settings.entry;
2   
3   
4   import java.util.Collection;
5   
6   import javax.naming.OperationNotSupportedException;
7   
8   import org.jscsi.parser.ProtocolDataUnit;
9   import org.jscsi.parser.login.LoginStage;
10  import org.jscsi.target.TargetServer;
11  import org.jscsi.target.settings.KeySet;
12  import org.jscsi.target.settings.NegotiationStatus;
13  import org.jscsi.target.settings.NegotiationType;
14  import org.jscsi.target.settings.SettingsNegotiator;
15  import org.jscsi.target.settings.TextKeyword;
16  import org.jscsi.target.settings.TextParameter;
17  import org.slf4j.Logger;
18  import org.slf4j.LoggerFactory;
19  
20  
21  /**
22   * {@link Entry} objects are used by instances {@link SettingsNegotiator} during text negotiation of connection and
23   * session parameter. For all parameters that are either declared by the iSCSI initiator or negotiated between the
24   * initiator and the target a separate {@link Entry} takes care of processing the respective <i>key=value</i> pair and
25   * returning the negotiated value, if appropriate.
26   * <p>
27   * For brevity, the term "negotiated" will be used in the following in a way that can either mean
28   * "declared or negotiated", unless the distinction is evident by context.
29   * 
30   * @author Andreas Ergenzinger, University of Konstanz
31   */
32  public abstract class Entry {
33  
34      private static final Logger LOGGER = LoggerFactory.getLogger(Entry.class);
35  
36      /**
37       * A {@link KeySet} containing all keys that can be used for negotiating this {@link Entry}'s value.
38       */
39      protected final KeySet keySet;
40  
41      /**
42       * Specifies if the {@link Entry}'s parameter is declared or negotiated.
43       */
44      protected final NegotiationType negotiationType;
45  
46      /**
47       * Determines during which stages this {@link Entry}'s parameters may be negotiated.
48       */
49      protected final Use use;
50  
51      /**
52       * This variable specifies the progress and necessity of negotiating the parameter managed by this {@link Entry}.
53       */
54      protected NegotiationStatus negotiationStatus;
55  
56      /**
57       * The currently valid value or <code>null</code>.
58       */
59      protected Object value;
60  
61      /**
62       * This variable is used to detect illegal attempts to renegotiate a previously negotiated or declared text
63       * parameter.
64       * <p>
65       * This variable will be set back to <code>false</code> after each negotiation task (login phase, or text parameter
66       * negotiation stage). Renegotiation accross stages/tasks can be prevented by initializing the {@link #use} variable
67       * accordingly.
68       * 
69       * @see #resetAlreadyNegotiated()
70       */
71      protected boolean alreadyNegotiated = false;
72  
73      /**
74       * Abstract constructor.
75       * 
76       * @param keySet contains all relevant keys
77       * @param negotiationType declared or negotiated
78       * @param use determines under which circumstances the parameter may be negotiated
79       * @param negotiationStatus indicates whether there is a default value or if the parameter must be negotiated
80       * @param defaultValue the default value or <code>null</code>
81       */
82      public Entry (final KeySet keySet, final NegotiationType negotiationType, final Use use, final NegotiationStatus negotiationStatus, Object defaultValue) {
83          this.keySet = keySet;
84          this.negotiationType = negotiationType;
85          this.use = use;
86          this.negotiationStatus = negotiationStatus;
87          this.value = defaultValue;
88      }
89  
90      /**
91       * Logs an error message containing all {@link #keySet} keys as well as the passed {@link String} parameter and
92       * indicates an unsuccessful negotiation by setting {@link #negotiationStatus} to {@link NegotiationStatus#REJECTED}
93       * .
94       * 
95       * @param logMessage
96       */
97      private void fail (final String logMessage) {
98          LOGGER.error("negotiation error " + keySet + ": " + logMessage);
99          negotiationStatus = NegotiationStatus.REJECTED;
100     }
101 
102     /**
103      * Parses the passed {@link String} parameter and returns a sub-class-specific {@link Object} which represents the
104      * the specified <i>value</i> part a <i>key=value</i> pair.
105      * 
106      * @param values the <i>value</i> part of a <i>key=value</i> pair
107      * @return sub-class-specific {@link Object} or <code>null</code> if the parameter violated the expected format
108      */
109     protected abstract Object parseOffer (TargetServer target, String values);
110 
111     /**
112      * This method is used for negotiating or declaring the {@link Entry}'s parameter.
113      * 
114      * @param loginStage specifying the current stage or phase of the connection whose parameters are to be negotiated
115      * @param leadingConnection <code>true</code> if the connection is the first connection in its session,
116      *            <code>false</code> if not
117      * @param initialPdu <code>true</code> if the <i>key=value</i> pair parameters have been sent in the first login
118      *            {@link ProtocolDataUnit} from the initiator, <code>false</code> if thy have not
119      * @param key the <i>key</i> part from the received <i>key=value</i> pair
120      * @param values the <i>value</i> part from the received <i>key=value</i> pair
121      * @param responseKeyValuePairs where the reply <i>key=value</i> pair will be added to if necessary
122      * @return <code>true</code> if everything went fine, <code>false</code> if errors occured
123      */
124     public final boolean negotiate (TargetServer target, final LoginStage loginStage, final boolean leadingConnection, final boolean initialPdu, final String key, final String values, final Collection<String> responseKeyValuePairs) {
125 
126         // (re)check key (just in case), this should have been checked before
127         // calling this method
128         if (!matchKey(key)) {
129             fail("\"" + key + "\" does not match key in" + keySet);
130             return false;
131         }
132 
133         // prevent renegotiation and remember this negotiation
134         if (alreadyNegotiated) {
135             fail("illegal renegotiation");
136             return false;
137         }
138         alreadyNegotiated = true;
139 
140         // check use code
141         if (!use.checkUse(loginStage, leadingConnection, initialPdu)) {
142             fail("wrong use: " + use + ", " + loginStage + ", " + leadingConnection + ", " + initialPdu);
143             return false;
144         }
145 
146         // transform values to appropriate type
147         final Object offer = parseOffer(target, values);
148 
149         if (offer == null) {
150             fail("value format error: " + values);
151             return false;
152         }
153 
154         // check if values are in the protocol-conform range/set of values
155         if (!inProtocolValueRange(offer)) {
156             fail("illegal values offered: " + values);
157             return false;
158         }
159 
160         // *** declare ***
161         if (negotiationType == NegotiationType.DECLARED) {
162             // save received value ...
163             processDeclaration(offer);
164             // ... and accept silently
165             negotiationStatus = NegotiationStatus.ACCEPTED;
166             return true;
167         }
168 
169         // *** negotiate ***
170         if (negotiationType == NegotiationType.NEGOTIATED) {
171 
172             String negotiatedValue;// will be returned as value part
173 
174             if (negotiationStatus == NegotiationStatus.IRRELEVANT)
175                 negotiatedValue = TextKeyword.IRRELEVANT;
176             else
177                 negotiatedValue = processNegotiation(offer);
178 
179             String reply;
180             // reply, remember outcome, log, and return
181             if (negotiatedValue == null) {// no commonly supported values
182                 reply = TextParameter.toKeyValuePair(key, TextKeyword.REJECT);
183                 responseKeyValuePairs.add(reply);
184                 fail("rejected value(s): " + values);
185                 return false;
186             }// else
187             reply = TextParameter.toKeyValuePair(key, negotiatedValue);
188             responseKeyValuePairs.add(reply);
189             return true;
190         }
191 
192         // we should not be here
193         fail("initialization error: negotiationType == null");
194         return false;
195     }
196 
197     /**
198      * Sets {@link #alreadyNegotiated} back to <code>false</code>.
199      * <p>
200      * This method must be used at the end of each negotiation task, i.e. at the end of the login phase and the FFP text
201      * negotiation stage.
202      */
203     public void resetAlreadyNegotiated () {
204         alreadyNegotiated = false;
205     }
206 
207     /**
208      * Returns the negotiated (or default) value as a {@link Boolean}.
209      * 
210      * @return the negotiated (or default) value as a {@link Boolean}
211      * @throws OperationNotSupportedException if {@link #value} is not of the boolean type
212      */
213     public Boolean getBooleanValue () throws OperationNotSupportedException {
214         throw new OperationNotSupportedException();
215     }
216 
217     /**
218      * Returns the negotiated (or default) value as an {@link Integer}.
219      * 
220      * @return the negotiated (or default) value as an {@link Integer}
221      * @throws OperationNotSupportedException if {@link #value} is not of the integer type
222      */
223     public Integer getIntegerValue () throws OperationNotSupportedException {
224         throw new OperationNotSupportedException();
225     }
226 
227     /**
228      * Returns the negotiated (or default) value as a {@link String}.
229      * 
230      * @return the negotiated (or default) value as a {@link String}
231      * @throws OperationNotSupportedException if {@link #value} is not a {@link String}
232      */
233     public String getStringValue () throws OperationNotSupportedException {
234         throw new OperationNotSupportedException();
235     }
236 
237     /**
238      * Returns <code>true</code> if one of the keys of {@link #keySet} equals the parameter and <code>false</code> if
239      * there is not match.
240      * 
241      * @param key the key to compare to the {@link #keySet} keys
242      * @return <code>true</code> if one of the keys of {@link #keySet} equals the parameter and <code>false</code> if
243      *         not
244      */
245     public final boolean matchKey (final String key) {
246         return keySet.matchKey(key);
247     }
248 
249     /**
250      * This method is used for checking if a sub-class-specific {@link Object}, representing a single, a range, or a
251      * list of values sent by the initiator, is illegal, according to the iSCSI standard.
252      * 
253      * @param values a sub-class-specific {@link Object}, representing a single, a range, or a list of values sent by
254      *            the initiator
255      * @return <code>false</code> if the iSCSI standard has been violated, <code>true</code> if not
256      */
257     protected abstract boolean inProtocolValueRange (Object values);
258 
259     /**
260      * Receives a sub-class-specific {@link Object}, representing a legal parameter value declared by the initiator and
261      * accepts it as the new {@link #value}.
262      * 
263      * @param values sub-class-specific representation of a single <i>value</i> declared by the initiator
264      */
265     protected abstract void processDeclaration (Object values);
266 
267     // returns null if reply is to be key=Reject
268     /**
269      * Receives a sub-class-specific {@link Object}, representing a list, a range, or a single legal parameter value
270      * offered by the initiator and tries to select a value from that offer. If none of the offered values is supported
271      * by the jSCSI Target, <code>null</code> is returned, otherwise the selection is accepted as the new {@link #value}
272      * and returned as a {@link String}. {@link #value}.
273      * 
274      * @param values a sub-class-specific {@link Object}, representing a list, a range, or a single legal parameter
275      *            value offered by the initiator
276      * @return the final, negotiated value or <code>null</code>, if the initiator's offer does not overlap with the
277      *         values supported by the jSCSI Target
278      */
279     protected abstract String processNegotiation (Object values);
280 
281     /**
282      * Returns {@link #negotiationStatus}.
283      * 
284      * @return {@link #negotiationStatus}
285      */
286     public final NegotiationStatus getNegotiationStatus () {
287         return negotiationStatus;
288     }
289 
290     /**
291      * Returns an exact copy of this {@link Entry}.
292      * 
293      * @return a copy of this {@link Entry}.
294      */
295     public abstract Entry copy ();
296 
297     /**
298      * Returns <code>true</code> if {@link #negotiationStatus} is {@link NegotiationStatus#ACCEPTED} and
299      * <code>false</code> if it is not.
300      * 
301      * @return <code>true</code> if {@link #negotiationStatus} is {@link NegotiationStatus#ACCEPTED}, <code>false</code>
302      *         if not
303      */
304     public boolean checkAccepted () {
305         return negotiationStatus == NegotiationStatus.ACCEPTED;
306     }
307 }