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) 
95  	    throws IOException, ClassNotFoundException{
96  	EntryClass eclass = Valid.notNull(
97  	    arg.get("eclass", null, EntryClass.class), 
98  	    "eclass cannot be null"
99  	); 
100 	String codebase = arg.get("codebase", null, String.class); 
101 	Object [] fields = Valid.notNull(
102 	    arg.get("fields", null, Object[].class), 
103 	    "fields array cannot be null"
104 	); 
105 	return true;
106     }
107     
108     EntryRep(GetArg arg) throws IOException, ClassNotFoundException{
109 	this(arg, check(arg));
110     }
111     
112     private EntryRep(GetArg arg, boolean check) 
113 	    throws IOException, ClassNotFoundException{
114 	eclass = arg.get("eclass", null, EntryClass.class);
115 	codebase = arg.get("codebase", null, String.class);
116 	fields = Valid.copy(arg.get("fields", null, Object[].class));
117 	flds = Collections.synchronizedList(Arrays.asList(fields != null ? fields : new Object[0]));
118     }
119     
120     private void writeObject(ObjectOutputStream out) throws IOException{
121 	synchronized (fields){
122 	    out.defaultWriteObject();
123 	}
124     }
125     
126     /**
127      * For clone and Reggie
128      */
129     public EntryRep(EntryRep copy, boolean replaceEntryClass){
130 	eclass = replaceEntryClass ? copy.eclass.getReplacement(): copy.eclass;
131 	codebase = copy.codebase;
132 	synchronized (copy.fields){
133 	    fields = copy.fields.clone();
134 	}
135 	flds = Collections.synchronizedList(Arrays.asList(fields != null ? fields : new Object[0]));
136     }
137     
138     /**
139      * Converts an Entry to an EntryRep.  Any exception that results
140      * is bundled up into a MarshalException.
141      */
142     private EntryRep(Entry entry, boolean needCodebase) throws RemoteException {
143 	EntryClassBase ecb = ClassMapper.toEntryClassBase(entry.getClass());
144 	eclass = ecb.eclass;
145 	codebase = needCodebase ? ecb.codebase : null;
146 	try {
147 	    EntryField[] efields = ClassMapper.getFields(entry.getClass());
148 	    fields = new Object[efields.length];
149 	    for (int i = efields.length; --i >= 0; ) {
150 		EntryField f = efields[i];
151 		Object val = f.field.get(entry);
152 		if (f.marshal && val != null)
153 		    val = new MarshalledWrapper(val);
154 		fields[i] = val;
155 	    }
156 	} catch (IOException e) {
157 	    throw new MarshalException("error marshalling arguments", e);
158 	} catch (IllegalAccessException e) {
159 	    throw new MarshalException("error marshalling arguments", e);
160 	}
161 	flds = Collections.synchronizedList(Arrays.asList(fields != null ? fields : new Object[0]));
162     }
163 
164     /**
165      * Convert back to an Entry.  If the Entry cannot be constructed,
166      * null is returned.  If a field cannot be unmarshalled, it is set
167      * to null.
168      */
169     public Entry get() {
170 	try {
171 	    Class clazz = eclass.toClass(codebase);
172 	    EntryField[] efields = ClassMapper.getFields(clazz);
173 	    Entry entry = (Entry)clazz.newInstance();
174 	    for (int i = efields.length; --i >= 0; ) {
175 		Object val = flds.get(i);
176 		EntryField f = efields[i];
177 		Field rf = f.field;
178 		try {
179 		    if (f.marshal && val != null)
180 			val = ((MarshalledWrapper) val).get();
181 		    rf.set(entry, val);
182 		} catch (Throwable e) {
183 		    if (e instanceof IllegalArgumentException) {
184 			// fix 4872566: work around empty exception message
185 			String msg = "unable to assign " +
186 			    ((val != null) ?
187 				"value of type " + val.getClass().getName() :
188 				"null") +
189 			    " to field " + rf.getDeclaringClass().getName() +
190 			    "." + rf.getName() + " of type " +
191 			    rf.getType().getName();
192 			e = new ClassCastException(msg).initCause(e);
193 		    }
194 		    RegistrarProxy.handleException(e);
195 		}
196 	    }
197 	    return entry;
198 	} catch (Throwable e) {
199 	    RegistrarProxy.handleException(e);
200 	}
201 	return null;
202     }
203 
204     /**
205      * We don't need this in the client or the server, but since we
206      * redefine equals we provide a minimal hashCode that works.
207      */
208     public int hashCode() {
209 	return eclass.hashCode();
210     }
211 
212     /**
213      * EntryReps are equal if they have the same class and the fields
214      * are pairwise equal.  This is really only needed in the server,
215      * but it's very convenient to have here.
216      */
217     public boolean equals(Object obj) {
218 	if (obj instanceof EntryRep) {
219 	    EntryRep entry = (EntryRep)obj;
220 	    if (!eclass.equals(entry.eclass) ||
221 		flds.size() != entry.flds.size())
222 		return false;
223 	    for (int i = flds.size(); --i >= 0; ) {
224 		if ((flds.get(i) == null && entry.flds.get(i) != null) ||
225 		    (flds.get(i) != null && !flds.get(i).equals(entry.flds.get(i))))
226 		    return false;
227 	    }	    
228 	    return true;
229 	}
230 	return false;
231     }
232 
233     /**
234      * Test if an entry matches a template.  
235      */
236     public boolean matchEntry(EntryRep tmpl) {
237 	if (!tmpl.eclass.isAssignableFrom(eclass) ||
238 	    tmpl.flds.size() > flds.size())
239 	    return false;
240 	for (int i = tmpl.flds.size(); --i >= 0; ) {
241 	    if (tmpl.flds.get(i) != null &&
242 		!tmpl.flds.get(i).equals(flds.get(i)))
243 		return false;
244 	}
245 	return true;
246     }
247 
248     /**
249      * Deep clone (which just means cloning the fields array too).
250      * This is really only needed in the server, but it's very
251      * convenient to have here.
252      */
253     @Override
254     public Object clone() {
255 	return new EntryRep(this, false);
256 	}
257 
258     /**
259      * Converts an array of Entry to an array of EntryRep.  If needCodebase
260      * is false, then the codebase of every EntryRep will be null.
261      */
262     public static EntryRep[] toEntryRep(Entry[] entries, boolean needCodebase)
263 	throws RemoteException
264     {
265 	EntryRep[] reps = null;
266 	if (entries != null) {
267 	    reps = new EntryRep[entries.length];
268 	    for (int i = entries.length; --i >= 0; ) {
269 		if (entries[i] != null) {
270 		    reps[i] = new EntryRep(entries[i], needCodebase);
271 		}
272 	    }
273 	}
274 	return reps;
275     }
276 
277     /** Converts an array of EntryRep to an array of Entry. */
278     public static Entry[] toEntry(EntryRep[] reps) {
279 	Entry[] entries = null;
280 	if (reps != null) {
281 	    entries = new Entry[reps.length];
282 	    for (int i = reps.length; --i >= 0; ) {
283 		entries[i] = reps[i].get();
284 	    }
285 	}
286 	return entries;
287     }
288     
289      private void readObject(ObjectInputStream in)
290 	throws IOException, ClassNotFoundException
291     {
292 	in.defaultReadObject();
293 	flds = Collections.synchronizedList(Arrays.asList(fields != null ? fields : new Object[0]));
294 }
295 }