View Javadoc

1   /*
2      Copyright 2008 Ramon Servadei
3   
4      Licensed under the Apache License, Version 2.0 (the "License");
5      you may not use this file except in compliance with the License.
6      You may obtain a copy of the License at
7   
8          http://www.apache.org/licenses/LICENSE-2.0
9   
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15   */
16  package fulmine.rpc;
17  
18  import java.util.List;
19  
20  import fulmine.model.field.FieldUtils;
21  import fulmine.model.field.IField;
22  import fulmine.protocol.specification.ByteReader;
23  import fulmine.protocol.specification.ByteWriter;
24  import fulmine.util.Utils;
25  import fulmine.util.collection.CollectionFactory;
26  import fulmine.util.reference.QuadValue;
27  
28  /**
29   * Standard implementation of an {@link IRpcCodec}. The encoding format in ABNF
30   * is as follows:
31   * 
32   * <pre>
33   * ENCODED      = MARKER &quot;|&quot; KEY ARGS
34   * MARKER       = 1*DIGIT ; the RPC marker ID
35   * KEY          = &quot;RpcKey&quot; (1*DIGIT) ; this is the RPC key
36   * ARGS         = 0*(&quot;|&quot; ARG)
37   * ARG          = 1*(ALPHA / DIGIT) ; argument values sent as their string value
38   * </pre>
39   * 
40   * The character '|' is special, so the arguments are further encoded/decoded to
41   * capture any '|' characters and replace them with '&&#124;' (i.e. & + the HTML
42   * escape sequence for '|')
43   * 
44   * @author Ramon Servadei
45   */
46  public class RpcCodec implements IRpcCodec
47  {
48      /**
49       * The name to give each re-constructed field during {@link #decode(byte[])}
50       * . Field names are not part of the {@link IRpcDefinition} and don't really
51       * mean much anyway so we use a single name each time with a number to
52       * signify the argument's position.
53       */
54      static final String FIELD_NAME = "field#";
55  
56      /** The delimiter for the encoding format */
57      private static final String DELIMITER = "|";
58  
59      /** The string for the escape code for the {@link #DELIMITER} */
60      final static String DELIMITER_ESCAPE_CODE = "&&#124;";
61  
62      /** The indexes for the various parts of the encoded string for the RPC */
63      private enum EncodingKeys
64      {
65          MARKER, RPC_KEY, REMOTE_CONTEXT, ARGS
66      }
67  
68      /** The registry, used to find the {@link IRpcDefinition} from the RPC key */
69      private final IRpcRegistry registry;
70  
71      /**
72       * Reference to the tokens extracted from the RPC data to use for
73       * {@link #getRpcKey()} and {@link #getArgs(byte[])}
74       */
75      private String[] rpcTokens;
76  
77      /**
78       * This is the count of non-argument value fields (e.g. RPC name, remote
79       * context, RPC marker)
80       */
81      private final static int META_PART_COUNT = 3;
82  
83      /**
84       * Construct the codec with the registry.
85       * 
86       * @param registry
87       *            the RPC definition registry
88       */
89      public RpcCodec(IRpcRegistry registry)
90      {
91          super();
92          this.registry = registry;
93      }
94  
95      public QuadValue<IRpcMarker, String, String, IField[]> decode(byte[] rpcData)
96      {
97          String data = ByteReader.fromBytes(rpcData);
98          final String[] tokens = getTokens(data);
99          IRpcMarker marker =
100             new RpcMarker(
101                 Integer.parseInt(tokens[EncodingKeys.MARKER.ordinal()]));
102         String remoteContextIdentity =
103             tokens[EncodingKeys.REMOTE_CONTEXT.ordinal()];
104         final String rpcKey = tokens[EncodingKeys.RPC_KEY.ordinal()];
105         IRpcDefinition definition = getRegistry().getDefinition(rpcKey);
106         if (definition == null)
107         {
108             throw new IllegalArgumentException(
109                 "No RPC definition, cannot decode '" + data + "', registry is "
110                     + getRegistry());
111         }
112         final Class<? extends IField>[] argumentTypes =
113             definition.getArgumentTypes();
114 
115         if (argumentTypes.length + META_PART_COUNT != tokens.length)
116         {
117             throw new IllegalArgumentException(
118                 "Invalid argument count, expected " + argumentTypes.length
119                     + " but got " + (tokens.length - META_PART_COUNT)
120                     + ", cannot decode '" + data + "'");
121         }
122         int length = tokens.length - META_PART_COUNT;
123         List<IField> argValues = CollectionFactory.newList(length);
124         for (int j = 0; j < length; j++)
125         {
126             Class<? extends IField> argType = argumentTypes[j];
127             final String fieldValue = tokens[j + META_PART_COUNT];
128             // decode the '|' special char from fieldValue
129             decodeSpecialChars(fieldValue);
130             argValues.add(FieldUtils.createFromString(argType, FIELD_NAME + j,
131                 fieldValue));
132         }
133         return new QuadValue<IRpcMarker, String, String, IField[]>(marker,
134             rpcKey, remoteContextIdentity,
135             argValues.toArray(new IField[argValues.size()]));
136     }
137 
138     public byte[] encode(IRpcMarker rpcMarker, String rpcKey,
139         String contextIdentity, IField[] args)
140     {
141         StringBuilder sb = new StringBuilder();
142         sb.append(rpcMarker.getId());
143         sb.append(DELIMITER);
144         sb.append(rpcKey);
145         sb.append(DELIMITER);
146         sb.append(contextIdentity);
147         for (IField field : args)
148         {
149             sb.append(DELIMITER);
150             sb.append(encodeSpecialChars(field.getValueAsString()));
151         }
152         return ByteWriter.getBytes(sb.toString());
153     }
154 
155     /**
156      * Get the RPC key from the RPC data. The RPC data is set using
157      * {@link #setData(byte[])}
158      * 
159      * @return the RPC key
160      */
161     public String getRpcKey()
162     {
163         Utils.nullCheck(this.rpcTokens, "no RPC tokens");
164         return this.rpcTokens[EncodingKeys.RPC_KEY.ordinal()];
165     }
166 
167     /**
168      * Get the RPC arguments from the RPC data. The RPC data is set using
169      * {@link #setData(byte[])}
170      * 
171      * @return the RPC arguments
172      */
173     public String[] getArgs()
174     {
175         Utils.nullCheck(this.rpcTokens, "no RPC tokens");
176         final int length = this.rpcTokens.length - META_PART_COUNT;
177         String[] args = new String[length];
178         if (length > 0)
179         {
180             // copy only the args from the tokens, the first 3 tokens are the
181             // RPC 'meta parts'
182             System.arraycopy(this.rpcTokens, META_PART_COUNT, args, 0,
183                 args.length);
184         }
185         return args;
186     }
187 
188     /**
189      * Set the RPC data to use for the {@link #getRpcKey()} and
190      * {@link #getArgs()}
191      * 
192      * @param rpcData
193      *            the RPC data in its byte form
194      */
195     public void setData(byte[] rpcData)
196     {
197         this.rpcTokens = getTokens(ByteReader.fromBytes(rpcData));
198     }
199 
200     /**
201      * Helper method to split the data into its constituent parts
202      * 
203      * @param data
204      *            the RPC data as a string
205      * @return the parts making up the RPC
206      */
207     private String[] getTokens(String data)
208     {
209         final String[] tokens = data.split("\\" + DELIMITER);
210         return tokens;
211     }
212 
213     private IRpcRegistry getRegistry()
214     {
215         return this.registry;
216     }
217 
218     String decodeSpecialChars(final String value)
219     {
220         return value.replaceAll(DELIMITER_ESCAPE_CODE, DELIMITER);
221     }
222 
223     String encodeSpecialChars(final String value)
224     {
225         return value.replaceAll("\\|", DELIMITER_ESCAPE_CODE);
226     }
227 
228 }