1 /*
2  * $RCSfile: RMIImageImpl.java,v $
3  *
4  * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
5  *
6  * Use is subject to license terms.
7  *
8  * $Revision: 1.1 $
9  * $Date: 2005/02/11 04:56:52 $
10  * $State: Exp $
11  */
12 package com.lightcrafts.media.jai.rmi;
13 
14 import java.awt.Rectangle;
15 import java.awt.RenderingHints;
16 import java.awt.image.RenderedImage;
17 import java.awt.image.renderable.RenderContext;
18 import java.io.Serializable;
19 import java.net.InetAddress;
20 import java.rmi.Naming;
21 import java.rmi.RemoteException;
22 import java.rmi.RMISecurityManager;
23 import java.rmi.server.UnicastRemoteObject;
24 import java.util.Hashtable;
25 import java.util.Vector;
26 import com.lightcrafts.mediax.jai.PlanarImage;
27 import com.lightcrafts.mediax.jai.PropertySource;
28 import com.lightcrafts.mediax.jai.RenderableOp;
29 import com.lightcrafts.mediax.jai.RenderedOp;
30 import com.lightcrafts.mediax.jai.remote.SerializableRenderedImage;
31 import com.lightcrafts.mediax.jai.remote.RemoteImagingException;
32 import com.lightcrafts.mediax.jai.util.ImagingListener;
33 import com.lightcrafts.media.jai.util.ImageUtil;
34 
35 /* A singleton class representing the serializable version of a null
36    property. This required because java.awt.Image.UndefinedProperty
37    is not serializable. */
38 class NullPropertyTag implements Serializable {
NullPropertyTag()39     NullPropertyTag() {}
40 }
41 
42 /**
43  * The server-side implementation of the RMIImage interface.  A
44  * RMIImageImpl has a RenderedImage source, acquired via one of three
45  * setSource() methods.  The first takes a RenderedImage directly as
46  * its parameter; this image is simply copied over the network using
47  * the normal RMI mechanisms.  Note that not every image can be
48  * transferred in this way -- for example, attempting to pass an
49  * OpImage that uses native code or that depends on the availability
50  * of a class not resident on the server as a parameter will cause an
51  * exception to be thrown.
52  *
53  * <p> The second and third ways of setting sources make use of the
54  * RenderedOp and RenderableOp classes to send a high-level
55  * description of an image chain based on operation names.  This
56  * chain will be copied over to the server using RMI, where it will be
57  * expanded into an OpImage chain using the server's registry.  This
58  * is the preferred method since it requires less data transfer and
59  * offers a better chance of success.  It may still fail if the
60  * sources or parameters of any operation in the chain are not
61  * serializable.
62  *
63  * <p> RMI requires all remote methods to declare `throws
64  * RemoteException' in their signatures.  It is up to the client to
65  * deal with errors.  A simple implementation of error handling may be
66  * found in the RemoteRenderedImage class.
67  *
68  * <p> This class contains a main() method that should be run on the
69  * server after starting the RMI registry.  The registry will then
70  * construct new instances of RMIImageImpl on demand.
71  *
72  * @see RMIImage
73  * @see RemoteImage
74  * @see RenderedOp
75  *
76  * @since EA3
77  *
78  */
79 public class RMIImageImpl implements RMIImage {
80     /** Tag to represent a null property. */
81     public static final Object NULL_PROPERTY = new NullPropertyTag();
82 
83     /** Identifier counter for the remote images. */
84     private static long idCounter = 0;
85 
86     /**
87      * The RenderedImage sources hashed by an ID string which must be unique
88      * across all possible clients of this object.
89      */
90     private static Hashtable sources = null;
91 
92     /**
93      * The PropertySources hashed by an ID string which must be unique
94      * across all possible clients of this object.
95      */
96     private static Hashtable propertySources = null;
97 
98     /**
99      * Adds a RenderedImage source to the Hashtable of sources.
100      *
101      * @param id A unique ID for the source.
102      * @param source The source RenderedImage.
103      * @param ps The PropertySource.
104      */
addSource(Long id, RenderedImage source, PropertySource ps)105     private static synchronized void addSource(Long id,
106                                                RenderedImage source,
107                                                PropertySource ps) {
108         // Create the Hashtables "just in time".
109         if(sources == null) {
110             sources = new Hashtable();
111             propertySources = new Hashtable();
112         }
113 
114         // Add the source and PropertySource.
115         sources.put(id, source);
116         propertySources.put(id, ps);
117     }
118 
119     /**
120      * Retrieve a PlanarImage source from the Hashtable of sources.
121      *
122      * @param id The unique ID of the source.
123      * @return The source.
124      */
getSource(Long id)125     private static PlanarImage getSource(Long id) throws RemoteException {
126         Object obj = null;
127         if(sources == null ||
128            (obj = sources.get(id)) == null) {
129             throw new RemoteException(JaiI18N.getString("RMIImageImpl2"));
130         }
131 
132         return (PlanarImage)obj;
133     }
134 
135     /**
136      * Retrieve a PropertySource from the Hashtable of PropertySources.
137      *
138      * @param id The unique ID of the source.
139      * @return The PropertySource.
140      */
getPropertySource(Long id)141     private static PropertySource getPropertySource(Long id)
142         throws RemoteException {
143         Object obj = null;
144         if(propertySources == null ||
145            (obj = propertySources.get(id)) == null) {
146             throw new RemoteException(JaiI18N.getString("RMIImageImpl2"));
147         }
148 
149         return (PropertySource)obj;
150     }
151 
152     /**
153      * Constructs a RMIImageImpl with a source to be specified
154      * later.
155      */
RMIImageImpl()156     public RMIImageImpl() throws RemoteException {
157         super();
158         try {
159             UnicastRemoteObject.exportObject(this);
160         } catch(RemoteException e) {
161             ImagingListener listener =
162                 ImageUtil.getImagingListener((RenderingHints)null);
163             String message = JaiI18N.getString("RMIImageImpl0");
164             listener.errorOccurred(message,
165                                    new RemoteImagingException(message, e),
166                                    this, false);
167 /*
168             e.printStackTrace();
169             throw new RuntimeException(JaiI18N.getString("RMIImageImpl0") +
170                                        e.getMessage());
171 */
172         }
173     }
174 
175     /**
176      * Returns the identifier of the remote image. This method should be
177      * called to return an identifier before any other methods are invoked.
178      * The same ID must be used in all subsequent references to the remote
179      * image.
180      */
getRemoteID()181     public synchronized Long getRemoteID() throws RemoteException {
182         return new Long(++idCounter);
183     }
184 
185     /**
186      * Sets the source of the image on the server side.  This source
187      * should ideally be a lightweight reference to an image available
188      * locally on the server or over a further network link (for
189      * example, an IIPOpImage that contains a URL but not actual image
190      * data).
191      *
192      * <p> Although it is legal to use any RenderedImage, one should be
193      * aware that a deep copy might be made and transmitted to the server.
194      *
195      * @param id An ID for the source which must be unique across all clients.
196      * @param source a RenderedImage source.
197      */
setSource(Long id, RenderedImage source)198     public void setSource(Long id, RenderedImage source)
199         throws RemoteException {
200             PlanarImage pi = PlanarImage.wrapRenderedImage(source);
201             addSource(id, pi, pi);
202     }
203 
204     /**
205      * Sets the source to a RenderedOp (i.e., an imaging DAG).
206      * This DAG will be copied over to the server where it will be
207      * transformed into an OpImage chain using the server's local
208      * OperationRegistry and available RenderedImageFactory objects.
209      *
210      * @param id An ID for the source which must be unique across all clients.
211      * @param source a RenderedOp source.
212      */
setSource(Long id, RenderedOp source)213     public void setSource(Long id,
214                           RenderedOp source)
215         throws RemoteException {
216         addSource(id, source.getRendering(), source);
217     }
218 
219     /**
220      * Sets the source to a RenderableOp defined by a renderable imaging
221      * DAG and a rendering context.  The entire RenderableImage
222      * DAG will be copied over to the server.
223      */
setSource(Long id, RenderableOp source, RenderContextProxy renderContextProxy)224     public void setSource(Long id,
225                           RenderableOp source,
226                           RenderContextProxy renderContextProxy)
227         throws RemoteException {
228             RenderContext renderContext =
229                 renderContextProxy.getRenderContext();
230             RenderedImage r = source.createRendering(renderContext);
231             PlanarImage pi = PlanarImage.wrapRenderedImage(r);
232             addSource(id, pi, pi);
233     }
234 
235     /**
236      * Disposes of any resouces allocated to the client object with
237      * the specified ID.
238      */
dispose(Long id)239     public void dispose(Long id)  throws RemoteException {
240         if(sources != null) {
241             sources.remove(id);
242             propertySources.remove(id);
243         }
244     }
245 
246     /** Gets a property from the property set of this image.  If the
247       property is undefined the constant NULL_PROPERTY is returned. */
getProperty(Long id, String name)248     public Object getProperty(Long id, String name) throws RemoteException {
249         PropertySource ps = getPropertySource(id);
250         Object property = ps.getProperty(name);
251         if(property == null ||
252            property.equals(java.awt.Image.UndefinedProperty)) {
253             property = NULL_PROPERTY;
254         }
255         return property;
256     }
257 
258     /**
259      * Returns a list of names recognized by getProperty().
260      *
261      * @return an array of Strings representing proeprty names.
262      */
getPropertyNames(Long id)263     public String[] getPropertyNames(Long id) throws RemoteException {
264         PropertySource ps = getPropertySource(id);
265         return ps.getPropertyNames();
266     }
267 
268     /** Returns the minimum X coordinate of the RMIImage. */
getMinX(Long id)269     public int getMinX(Long id) throws RemoteException {
270         return getSource(id).getMinX();
271     }
272 
273     /** Returns the smallest X coordinate to the right of the RMIImage. */
getMaxX(Long id)274     public int getMaxX(Long id) throws RemoteException {
275         return getSource(id).getMaxX();
276     }
277 
278     /** Returns the minimum Y coordinate of the RMIImage. */
getMinY(Long id)279     public int getMinY(Long id) throws RemoteException {
280         return getSource(id).getMinY();
281     }
282 
283     /** Returns the smallest Y coordinate below the RMIImage. */
getMaxY(Long id)284     public int getMaxY(Long id) throws RemoteException {
285         return getSource(id).getMaxY();
286     }
287 
288     /** Returns the width of the RMIImage. */
getWidth(Long id)289     public int getWidth(Long id) throws RemoteException {
290         return getSource(id).getWidth();
291     }
292 
293     /** Returns the height of the RMIImage. */
getHeight(Long id)294     public int getHeight(Long id) throws RemoteException {
295         return getSource(id).getHeight();
296     }
297 
298     /** Returns the width of a tile in pixels. */
getTileWidth(Long id)299     public int getTileWidth(Long id) throws RemoteException {
300         return getSource(id).getTileWidth();
301     }
302 
303     /** Returns the height of a tile in pixels. */
getTileHeight(Long id)304     public int getTileHeight(Long id) throws RemoteException {
305         return getSource(id).getTileHeight();
306     }
307 
308     /**
309      * Returns the X coordinate of the upper-left pixel of tile (0, 0).
310      */
getTileGridXOffset(Long id)311     public int getTileGridXOffset(Long id) throws RemoteException {
312         return getSource(id).getTileGridXOffset();
313     }
314 
315     /**
316      * Returns the Y coordinate of the upper-left pixel of tile (0, 0).
317      */
getTileGridYOffset(Long id)318     public int getTileGridYOffset(Long id) throws RemoteException {
319         return getSource(id).getTileGridYOffset();
320     }
321 
322     /** Returns the index of the leftmost column of tiles. */
getMinTileX(Long id)323     public int getMinTileX(Long id) throws RemoteException {
324         return getSource(id).getMinTileX();
325     }
326 
327     /**
328      * Returns the number of tiles along the tile grid in the horizontal
329      * direction.
330      */
getNumXTiles(Long id)331     public int getNumXTiles(Long id) throws RemoteException {
332 	return getSource(id).getNumXTiles();
333     }
334 
335     /** Returns the index of the uppermost row of tiles. */
getMinTileY(Long id)336     public int getMinTileY(Long id) throws RemoteException {
337         return getSource(id).getMinTileY();
338     }
339 
340     /**
341      * Returns the number of tiles along the tile grid in the vertical
342      * direction.
343      */
getNumYTiles(Long id)344     public int getNumYTiles(Long id) throws RemoteException {
345 	return getSource(id).getNumYTiles();
346     }
347 
348     /** Returns the index of the rightmost column of tiles. */
getMaxTileX(Long id)349     public int getMaxTileX(Long id) throws RemoteException {
350 	return getSource(id).getMaxTileX();
351     }
352 
353     /** Returns the index of the bottom row of tiles. */
getMaxTileY(Long id)354     public int getMaxTileY(Long id) throws RemoteException {
355 	return getSource(id).getMaxTileY();
356     }
357 
358     /** Returns the SampleModel associated with this image. */
getSampleModel(Long id)359     public SampleModelProxy getSampleModel(Long id) throws RemoteException {
360         return new SampleModelProxy(getSource(id).getSampleModel());
361     }
362 
363     /** Returns the ColorModel associated with this image. */
getColorModel(Long id)364     public ColorModelProxy getColorModel(Long id) throws RemoteException {
365         return new ColorModelProxy(getSource(id).getColorModel());
366 
367     }
368 
369     /**
370      * Returns a vector of RenderedImages that are the sources of
371      * image data for this RMIImage.  Note that this method
372      * will often return an empty vector.
373      */
getSources(Long id)374     public Vector getSources(Long id) throws RemoteException {
375         Vector sourceVector = getSource(id).getSources();
376         int size = sourceVector.size();
377         boolean isCloned = false;
378         for(int i = 0; i < size; i++) {
379             RenderedImage img = (RenderedImage)sourceVector.get(i);
380             if(!(img instanceof Serializable)) {
381                 if(!isCloned) {
382                     sourceVector = (Vector)sourceVector.clone();
383                 }
384                 sourceVector.set(i, new SerializableRenderedImage(img, false));
385             }
386         }
387         return sourceVector;
388     }
389 
390     /** Returns a Rectangle indicating the image bounds. */
getBounds(Long id)391     public Rectangle getBounds(Long id) throws RemoteException {
392         return getSource(id).getBounds();
393     }
394 
395     /**
396      * Returns tile (x, y).  Note that x and y are indices into the
397      * tile array, not pixel locations.  Unlike in the true RenderedImage
398      * interface, the Raster that is returned should be considered a copy.
399      *
400      * @param id An ID for the source which must be unique across all clients.
401      * @param tileX the X index of the requested tile in the tile array.
402      * @param tileY the Y index of the requested tile in the tile array.
403      * @return the tile as a Raster.
404      */
getTile(Long id, int tileX, int tileY)405     public RasterProxy getTile(Long id, int tileX, int tileY)
406         throws RemoteException {
407         return new RasterProxy(getSource(id).getTile(tileX, tileY));
408     }
409 
410     /**
411      * Returns the entire image as a single Raster.
412      *
413      * @return a Raster containing a copy of this image's data.
414      */
getData(Long id)415     public RasterProxy getData(Long id) throws RemoteException {
416         return new RasterProxy(getSource(id).getData());
417     }
418 
419     /**
420      * Returns an arbitrary rectangular region of the RenderedImage
421      * in a Raster.  The rectangle of interest will be clipped against
422      * the image bounds.
423      *
424      * @param id An ID for the source which must be unique across all clients.
425      * @param rect the region of the RenderedImage to be returned.
426      * @return a Raster containing a copy of the desired data.
427      */
getData(Long id, Rectangle bounds)428     public RasterProxy getData(Long id, Rectangle bounds)
429         throws RemoteException {
430         RasterProxy rp = null;
431         if(bounds == null) {
432             rp = getData(id);
433         } else {
434             bounds = bounds.intersection(getBounds(id));
435             rp = new RasterProxy(getSource(id).getData(bounds));
436         }
437         return rp;
438     }
439 
440     /**
441      * Returns the same result as getData(Rectangle) would for the
442      * same rectangular region.
443      */
copyData(Long id, Rectangle bounds)444     public RasterProxy copyData(Long id, Rectangle bounds)
445         throws RemoteException {
446         return getData(id, bounds);
447     }
448 
449     /**
450      * Starts a server on a given port.  The RMI registry must be running
451      * on the server host.
452      *
453      * <p> The usage of this class is
454      *
455      * <pre>
456      * java -Djava.rmi.server.codebase=file:$JAI/lib/jai.jar \
457      * -Djava.rmi.server.useCodebaseOnly=false \
458      * -Djava.security.policy=\
459      * file:`pwd`/policy com.lightcrafts.media.jai.rmi.RMIImageImpl \
460      * [-host hostName] [-port portNumber]
461      * </pre>
462      *
463      * The default host is the local host and the default port is 1099.
464      *
465      * @param args the port number as a command-line argument.
466      */
main(String [] args)467     public static void main(String [] args) {
468         // Set the security manager.
469         if(System.getSecurityManager() == null) {
470             System.setSecurityManager(new RMISecurityManager());
471         }
472 
473         // Set the host name and port number.
474         String host = null;
475         int port = 1099; // default port is 1099
476         for(int i = 0; i < args.length; i++) {
477             if(args[i].equalsIgnoreCase("-host")) {
478                 host = args[++i];
479             } else if(args[i].equalsIgnoreCase("-port")) {
480                 port = Integer.parseInt(args[++i]);
481             }
482         }
483 
484         // Default to the local host if the host was not specified.
485         if(host == null) {
486             try {
487                 host = InetAddress.getLocalHost().getHostAddress();
488             } catch(java.net.UnknownHostException e) {
489                 System.err.println(JaiI18N.getString("RMIImageImpl1") +
490                                    e.getMessage());
491                 e.printStackTrace();
492             }
493         }
494 
495         System.out.println(JaiI18N.getString("RMIImageImpl3")+" "+
496                            host+":"+port);
497 
498         try {
499             RMIImageImpl im = new RMIImageImpl();
500             String serverName =
501                 new String("rmi://" +
502                            host + ":" + port + "/" +
503                            RMIImage.RMI_IMAGE_SERVER_NAME);
504             System.out.println(JaiI18N.getString("RMIImageImpl4")+" \""+
505                                serverName+"\".");
506             Naming.rebind(serverName, im);
507             System.out.println(JaiI18N.getString("RMIImageImpl5"));
508         } catch (Exception e) {
509             System.err.println(JaiI18N.getString("RMIImageImpl0") +
510                                e.getMessage());
511             e.printStackTrace();
512         }
513     }
514 }
515