View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.river.reggie.proxy;
19  
20  import org.apache.river.proxy.MarshalledWrapper;
21  import org.apache.river.reggie.proxy.ClassMapper.EntryField;
22  import java.io.IOException;
23  import java.io.InvalidObjectException;
24  import java.io.ObjectInputStream;
25  import java.io.ObjectOutputStream;
26  import java.io.ObjectStreamField;
27  import java.io.Serializable;
28  import java.lang.reflect.Field;
29  import java.rmi.MarshalException;
30  import java.rmi.RemoteException;
31  import java.util.Arrays;
32  import java.util.Collections;
33  import java.util.List;
34  import net.jini.core.entry.Entry;
35  import org.apache.river.api.io.AtomicSerial;
36  import org.apache.river.api.io.AtomicSerial.GetArg;
37  import org.apache.river.api.io.Valid;
38  
39  /**
40   * An EntryRep contains the fields of an Entry packaged up for
41   * transmission between client-side proxies and the registrar server.
42   * Instances are never visible to clients, they are private to the
43   * communication between the proxies and the server.
44   * <p>
45   * This class only has a bare minimum of methods, to minimize
46   * the amount of code downloaded into clients.
47   *
48   * @author Sun Microsystems, Inc.
49   *
50   */
51  @AtomicSerial
52  public final class EntryRep implements Serializable, Cloneable {
53  
54      private static final long serialVersionUID = 2L;
55      private static final ObjectStreamField[] serialPersistentFields = 
56      { 
57          /** @serialField The Class of the Entry converted to EntryClass. */
58          new ObjectStreamField("eclass", EntryClass.class),
59          /** @serialField The codebase of the entry class. */
60          new ObjectStreamField("codebase", String.class),
61  	/** @serialField The public fields of the Entry, each converted as necessary to
62  	 * a MarshalledWrapper (or left as is if of known java.lang immutable
63  	 * type).  The fields are in super- to subclass order. */
64          new ObjectStreamField("fields", Object[].class)
65      };
66  
67      /**
68       * The Class of the Entry converted to EntryClass.
69       *
70       * @serial
71       */
72      public final EntryClass eclass;
73      /**
74       * The codebase of the entry class.
75       * 
76       * @serial
77       */
78      public final String codebase;
79      /**
80       * The public fields of the Entry, each converted as necessary to
81       * a MarshalledWrapper (or left as is if of known java.lang immutable
82       * type).  The fields are in super- to subclass order.
83       *
84       * @serial
85       */
86      private final Object[] fields; // Individual fields were mutated by RegistrarImpl
87  
88      transient List flds; // Reggie now uses flds to access fields.
89     
90      public List fields(){
91  	return flds;
92      }
93      
94      private static boolean check(GetArg arg) throws IOException{
95  	EntryClass eclass = Valid.notNull(
96  	    arg.get("eclass", null, EntryClass.class), 
97  	    "eclass cannot be null"
98  	); 
99  	String codebase = arg.get("codebase", null, String.class); 
100 	Object [] fields = Valid.notNull(
101 	    arg.get("fields", null, Object[].class), 
102 	    "fields array cannot be null"
103 	); 
104 	return true;
105     }
106     
107     EntryRep(GetArg arg) throws IOException{
108 	this(arg, check(arg));
109     }
110     
111     private EntryRep(GetArg arg, boolean check) throws IOException{
112 	eclass = arg.get("eclass", null, EntryClass.class);
113 	codebase = arg.get("codebase", null, String.class);
114 	fields = Valid.copy(arg.get("fields", null, Object[].class));
115 	flds = Collections.synchronizedList(Arrays.asList(fields != null ? fields : new Object[0]));
116     }
117     
118     private void writeObject(ObjectOutputStream out) throws IOException{
119 	synchronized (fields){
120 	    out.defaultWriteObject();
121 	}
122     }
123     
124     /**
125      * For clone and Reggie
126      */
127     public EntryRep(EntryRep copy, boolean replaceEntryClass){
128 	eclass = replaceEntryClass ? copy.eclass.getReplacement(): copy.eclass;
129 	codebase = copy.codebase;
130 	synchronized (copy.fields){
131 	    fields = copy.fields.clone();
132 	}
133 	flds = Collections.synchronizedList(Arrays.asList(fields != null ? fields : new Object[0]));
134     }
135     
136     /**
137      * Converts an Entry to an EntryRep.  Any exception that results
138      * is bundled up into a MarshalException.
139      */
140     private EntryRep(Entry entry, boolean needCodebase) throws RemoteException {
141 	EntryClassBase ecb = ClassMapper.toEntryClassBase(entry.getClass());
142 	eclass = ecb.eclass;
143 	codebase = needCodebase ? ecb.codebase : null;
144 	try {
145 	    EntryField[] efields = ClassMapper.getFields(entry.getClass());
146 	    fields = new Object[efields.length];
147 	    for (int i = efields.length; --i >= 0; ) {
148 		EntryField f = efields[i];
149 		Object val = f.field.get(entry);
150 		if (f.marshal && val != null)
151 		    val = new MarshalledWrapper(val);
152 		fields[i] = val;
153 	    }
154 	} catch (IOException e) {
155 	    throw new MarshalException("error marshalling arguments", e);
156 	} catch (IllegalAccessException e) {
157 	    throw new MarshalException("error marshalling arguments", e);
158 	}
159 	flds = Collections.synchronizedList(Arrays.asList(fields != null ? fields : new Object[0]));
160     }
161 
162     /**
163      * Convert back to an Entry.  If the Entry cannot be constructed,
164      * null is returned.  If a field cannot be unmarshalled, it is set
165      * to null.
166      */
167     public Entry get() {
168 	try {
169 	    Class clazz = eclass.toClass(codebase);
170 	    EntryField[] efields = ClassMapper.getFields(clazz);
171 	    Entry entry = (Entry)clazz.newInstance();
172 	    for (int i = efields.length; --i >= 0; ) {
173 		Object val = flds.get(i);
174 		EntryField f = efields[i];
175 		Field rf = f.field;
176 		try {
177 		    if (f.marshal && val != null)
178 			val = ((MarshalledWrapper) val).get();
179 		    rf.set(entry, val);
180 		} catch (Throwable e) {
181 		    if (e instanceof IllegalArgumentException) {
182 			// fix 4872566: work around empty exception message
183 			String msg = "unable to assign " +
184 			    ((val != null) ?
185 				"value of type " + val.getClass().getName() :
186 				"null") +
187 			    " to field " + rf.getDeclaringClass().getName() +
188 			    "." + rf.getName() + " of type " +
189 			    rf.getType().getName();
190 			e = new ClassCastException(msg).initCause(e);
191 		    }
192 		    RegistrarProxy.handleException(e);
193 		}
194 	    }
195 	    return entry;
196 	} catch (Throwable e) {
197 	    RegistrarProxy.handleException(e);
198 	}
199 	return null;
200     }
201 
202     /**
203      * We don't need this in the client or the server, but since we
204      * redefine equals we provide a minimal hashCode that works.
205      */
206     public int hashCode() {
207 	return eclass.hashCode();
208     }
209 
210     /**
211      * EntryReps are equal if they have the same class and the fields
212      * are pairwise equal.  This is really only needed in the server,
213      * but it's very convenient to have here.
214      */
215     public boolean equals(Object obj) {
216 	if (obj instanceof EntryRep) {
217 	    EntryRep entry = (EntryRep)obj;
218 	    if (!eclass.equals(entry.eclass) ||
219 		flds.size() != entry.flds.size())
220 		return false;
221 	    for (int i = flds.size(); --i >= 0; ) {
222 		if ((flds.get(i) == null && entry.flds.get(i) != null) ||
223 		    (flds.get(i) != null && !flds.get(i).equals(entry.flds.get(i))))
224 		    return false;
225 	    }	    
226 	    return true;
227 	}
228 	return false;
229     }
230 
231     /**
232      * Test if an entry matches a template.  
233      */
234     public boolean matchEntry(EntryRep tmpl) {
235 	if (!tmpl.eclass.isAssignableFrom(eclass) ||
236 	    tmpl.flds.size() > flds.size())
237 	    return false;
238 	for (int i = tmpl.flds.size(); --i >= 0; ) {
239 	    if (tmpl.flds.get(i) != null &&
240 		!tmpl.flds.get(i).equals(flds.get(i)))
241 		return false;
242 	}
243 	return true;
244     }
245 
246     /**
247      * Deep clone (which just means cloning the fields array too).
248      * This is really only needed in the server, but it's very
249      * convenient to have here.
250      */
251     @Override
252     public Object clone() {
253 	return new EntryRep(this, false);
254 	}
255 
256     /**
257      * Converts an array of Entry to an array of EntryRep.  If needCodebase
258      * is false, then the codebase of every EntryRep will be null.
259      */
260     public static EntryRep[] toEntryRep(Entry[] entries, boolean needCodebase)
261 	throws RemoteException
262     {
263 	EntryRep[] reps = null;
264 	if (entries != null) {
265 	    reps = new EntryRep[entries.length];
266 	    for (int i = entries.length; --i >= 0; ) {
267 		if (entries[i] != null) {
268 		    reps[i] = new EntryRep(entries[i], needCodebase);
269 		}
270 	    }
271 	}
272 	return reps;
273     }
274 
275     /** Converts an array of EntryRep to an array of Entry. */
276     public static Entry[] toEntry(EntryRep[] reps) {
277 	Entry[] entries = null;
278 	if (reps != null) {
279 	    entries = new Entry[reps.length];
280 	    for (int i = reps.length; --i >= 0; ) {
281 		entries[i] = reps[i].get();
282 	    }
283 	}
284 	return entries;
285     }
286     
287      private void readObject(ObjectInputStream in)
288 	throws IOException, ClassNotFoundException
289     {
290 	in.defaultReadObject();
291 	flds = Collections.synchronizedList(Arrays.asList(fields != null ? fields : new Object[0]));
292 }
293 }