001: /*
002: * $RCSfile: RemoteRenderableOp.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.2 $
009: * $Date: 2005/05/12 18:24:35 $
010: * $State: Exp $
011: */package javax.media.jai.remote;
012:
013: import java.awt.RenderingHints;
014: import java.awt.geom.Rectangle2D;
015: import java.awt.geom.AffineTransform;
016: import java.awt.image.ImageConsumer;
017: import java.awt.image.Raster;
018: import java.awt.image.RenderedImage;
019: import java.awt.image.renderable.ContextualRenderedImageFactory;
020: import java.awt.image.renderable.ParameterBlock;
021: import java.awt.image.renderable.RenderContext;
022: import java.awt.image.renderable.RenderableImage;
023: import java.awt.image.renderable.RenderableImageOp;
024: import java.io.IOException;
025: import java.io.ObjectInputStream;
026: import java.io.ObjectOutputStream;
027: import java.io.Serializable;
028: import java.text.MessageFormat;
029: import java.util.Enumeration;
030: import java.util.Hashtable;
031: import java.util.Locale;
032: import java.util.Map;
033: import java.util.Vector;
034: import javax.media.jai.JAI;
035: import javax.media.jai.OperationRegistry;
036: import javax.media.jai.PlanarImage;
037: import javax.media.jai.PropertyChangeEventJAI;
038: import javax.media.jai.PropertyGenerator;
039: import javax.media.jai.PropertySource;
040: import javax.media.jai.RenderedOp;
041: import javax.media.jai.RenderableOp;
042: import javax.media.jai.RegistryMode;
043: import javax.media.jai.WritablePropertySource;
044: import javax.media.jai.remote.SerializableRenderedImage;
045: import javax.media.jai.registry.RemoteCRIFRegistry;
046: import javax.media.jai.util.ImagingException;
047: import javax.media.jai.util.ImagingListener;
048: import com.sun.media.jai.util.PropertyUtil;
049: import com.sun.media.jai.rmi.RasterProxy;
050: import com.sun.media.jai.util.ImageUtil;
051:
052: /**
053: * A subclass of <code>RenderableOp</code> for remote operations. This
054: * class represents a node in a remote renderable imaging
055: * chain. A <code>RemoteRenderableOp</code> stores a protocol name (as a
056: * <code>String</code>), a server name (as a <code>String</code>), an
057: * operation name (as a <code>String</code>), and a
058: * <code>ParameterBlock</code> containing sources and miscellaneous
059: * parameters.
060: *
061: * <p> By virtue of being a subclass of <code>RemoteRenderableOp</code>,
062: * this class participates in Java Bean-style events as specified by
063: * <code>RenderableOp</code>. <code>RemoteRenderableOp</code>s add the
064: * server name and the protocol name to the critical attributes, the
065: * editing (chaging) of which may cause a <code>PropertyChangeEventJAI</code>
066: * to be emitted.
067: *
068: * @see javax.media.jai.RenderableOp
069: *
070: * @since JAI 1.1
071: */
072: public class RemoteRenderableOp extends RenderableOp {
073:
074: /** The name of the protocol this class provides an implementation for. */
075: protected String protocolName;
076:
077: /** The name of the server. */
078: protected String serverName;
079:
080: // The RemoteCRIF used to create a rendering.
081: private transient RemoteCRIF remoteCRIF = null;
082:
083: // The NegotiableCapabilitySet representing the negotiated values.
084: private NegotiableCapabilitySet negotiated = null;
085:
086: // A reference to the RMIServerProxy which connects to the corresponding
087: // RenderableOp on the server. Holding this reference ensures that the
088: // RMIServerProxy doesn't get garbage-collected.
089: private transient RenderedImage linkToRemoteOp;
090:
091: /**
092: * Constructs a <code>RemoteRenderableOp</code> using the default
093: * operation registry, given the name of the remote imaging protocol,
094: * the name of the server to perform the operation on, the name of the
095: * operation to be performed remotely and a <code>ParameterBlock</code>
096: * containing <code>RenderableImage</code> sources and other parameters.
097: * Any <code>RenderedImage</code> sources referenced by the
098: * <code>ParameterBlock</code> will be ignored.
099: *
100: * <p> An <code>IllegalArgumentException</code> may
101: * be thrown by the protocol specific classes at a later point, if
102: * null is provided as the serverName argument and null is not
103: * considered a valid server name by the specified protocol.
104: *
105: * @param protocolName The protocol name as a <code>String</code>.
106: * @param serverName The server name as a <code>String</code>.
107: * @param opName The operation name.
108: * @param pb The sources and other parameters. If
109: * <code>null</code>, it is assumed that this node has
110: * no sources and parameters.
111: *
112: * @throws IllegalArgumentException if <code>protocolName</code> is
113: * <code>null</code>.
114: * @throws IllegalArgumentException if <code>opName</code> is
115: * <code>null</code>.
116: */
117: public RemoteRenderableOp(String protocolName, String serverName,
118: String opName, ParameterBlock pb) {
119: this (null, protocolName, serverName, opName, pb);
120: }
121:
122: /**
123: * Constructs a <code>RemoteRenderableOp</code> using the specified
124: * operation registry, given the name of the remote imaging protocol,
125: * the name of the server to perform the operation on, the name of the
126: * operation to be performed remotely and a <code>ParameterBlock</code>
127: * containing <code>RenderableImage</code> sources and other parameters.
128: * Any <code>RenderedImage</code> sources referenced by the
129: * <code>ParameterBlock</code> will be ignored.
130: *
131: * <p> An <code>IllegalArgumentException</code> may
132: * be thrown by the protocol specific classes at a later point, if
133: * null is provided as the serverName argument and null is not
134: * considered a valid server name by the specified protocol.
135: *
136: * @param registry The <code>OperationRegistry</code> to be used for
137: * instantiation. if <code>null</code>, the default
138: * registry is used.
139: * @param protocolName The protocol name as a <code>String</code>.
140: * @param serverName The server name as a <code>String</code>.
141: * @param opName The operation name.
142: * @param pb The sources and other parameters. If
143: * <code>null</code>, it is assumed that this node has
144: * no sources and parameters.
145: *
146: * @throws IllegalArgumentException if <code>protocolName</code> is
147: * <code>null</code>.
148: * @throws IllegalArgumentException if <code>opName</code> is
149: * <code>null</code>.
150: */
151: public RemoteRenderableOp(OperationRegistry registry,
152: String protocolName, String serverName, String opName,
153: ParameterBlock pb) {
154:
155: super (registry, opName, pb);
156:
157: if (protocolName == null || opName == null) {
158: throw new IllegalArgumentException();
159: }
160:
161: this .protocolName = protocolName;
162: this .serverName = serverName;
163: }
164:
165: /**
166: * Returns the name of the <code>RegistryMode</code> corresponding to
167: * this <code>RenderableOp</code>. This method overrides the
168: * implementation in <code>RenderableOp</code> to always returns the
169: * <code>String</code> "remoteRenderable".
170: */
171: public String getRegistryModeName() {
172: return RegistryMode.getMode("remoteRenderable").getName();
173: }
174:
175: /**
176: * Returns the <code>String</code> that identifies the server.
177: */
178: public String getServerName() {
179: return serverName;
180: }
181:
182: /**
183: * Sets a <code>String</code> identifying the server.
184: *
185: * <p> If the supplied name does not equal the current server name, a
186: * <code>PropertyChangeEventJAI</code> named "ServerName"
187: * will be fired. The oldValue field in the
188: * <code>PropertyChangeEventJAI</code> will contain the old server
189: * name <code>String</code> and the newValue field will contain the
190: * new server name <code>String</code>.
191: *
192: * @param serverName A <code>String</code> identifying the server.
193: * @throws IllegalArgumentException if serverName is null.
194: */
195: public void setServerName(String serverName) {
196:
197: if (serverName == null)
198: throw new IllegalArgumentException(JaiI18N
199: .getString("Generic2"));
200:
201: if (serverName.equalsIgnoreCase(this .serverName))
202: return;
203: String oldServerName = this .serverName;
204: this .serverName = serverName;
205: fireEvent("ServerName", oldServerName, serverName);
206: nodeSupport.resetPropertyEnvironment(false);
207: }
208:
209: /**
210: * Returns the <code>String</code> that identifies the remote imaging
211: * protocol.
212: */
213: public String getProtocolName() {
214: return protocolName;
215: }
216:
217: /**
218: * Sets a <code>String</code> identifying the remote imaging protocol.
219: *
220: * <p> If the supplied name does not equal the current protocol name, a
221: * <code>PropertyChangeEventJAI</code> named "ProtocolName"
222: * will be fired. The oldValue field in the
223: * <code>PropertyChangeEventJAI</code> will contain the old protocol
224: * name <code>String</code> and the newValue field will contain the
225: * new protocol name <code>String</code>.
226: *
227: * @param protocolName A <code>String</code> identifying the protocol.
228: * @throws IllegalArgumentException if protocolName is null.
229: */
230: public void setProtocolName(String protocolName) {
231:
232: if (protocolName == null)
233: throw new IllegalArgumentException(JaiI18N
234: .getString("Generic1"));
235:
236: if (protocolName.equalsIgnoreCase(this .protocolName))
237: return;
238:
239: String oldProtocolName = this .protocolName;
240: this .protocolName = protocolName;
241: fireEvent("ProtocolName", oldProtocolName, protocolName);
242: nodeSupport.resetPropertyEnvironment(false);
243: }
244:
245: /**
246: * Sets the protocol name and the server name of this
247: * <code>RemoteRenderableOp</code> to the specified arguments..
248: *
249: * <p> If both the supplied protocol name and the supplied server
250: * name values do not equal the current values, a
251: * <code>PropertyChangeEventJAI</code> named "ProtocolAndServerName"
252: * will be fired. The oldValue field in the
253: * <code>PropertyChangeEventJAI</code> will contain a two element
254: * array of <code>String</code>s, the old protocol name being the
255: * first element and the old server name being the second. Similarly
256: * the newValue field of the <code>PropertyChangeEventJAI</code> will
257: * contain a two element array of <code>String</code>s, the new protocol
258: * name being the first element and the new server name being the
259: * second. If only the supplied protocol name does not equal
260: * the current protocol name, a <code>PropertyChangeEventJAI</code>
261: * named "ProtocolName" will be fired. If only the supplied server
262: * name does not equal the current server name, a
263: * <code>PropertyChangeEventJAI</code> named "ServerName"
264: * will be fired.
265: *
266: * @param protocolName A <code>String</code> identifying the protocol.
267: * @param serverName A <code>String</code> identifying the server.
268: * @throws IllegalArgumentException if protocolName is null.
269: * @throws IllegalArgumentException if serverName is null.
270: */
271: public void setProtocolAndServerNames(String protocolName,
272: String serverName) {
273:
274: if (serverName == null)
275: throw new IllegalArgumentException(JaiI18N
276: .getString("Generic2"));
277:
278: if (protocolName == null)
279: throw new IllegalArgumentException(JaiI18N
280: .getString("Generic1"));
281:
282: boolean protocolNotChanged = protocolName
283: .equalsIgnoreCase(this .protocolName);
284: boolean serverNotChanged = serverName
285: .equalsIgnoreCase(this .serverName);
286:
287: if (protocolNotChanged) {
288: if (serverNotChanged)
289: // Neither changed
290: return;
291: else {
292: // Only serverName changed
293: setServerName(serverName);
294: return;
295: }
296: } else {
297: if (serverNotChanged) {
298: // Only protocolName changed
299: setProtocolName(protocolName);
300: return;
301: }
302: }
303:
304: String oldProtocolName = this .protocolName;
305: String oldServerName = this .serverName;
306: this .protocolName = protocolName;
307: this .serverName = serverName;
308:
309: // Both changed
310: fireEvent("ProtocolAndServerName", new String[] {
311: oldProtocolName, oldServerName }, new String[] {
312: protocolName, serverName });
313: nodeSupport.resetPropertyEnvironment(false);
314: }
315:
316: // Fire an event to all listeners registered with this node.
317: private void fireEvent(String propName, Object oldVal, Object newVal) {
318: if (eventManager != null) {
319: Object eventSource = eventManager
320: .getPropertyChangeEventSource();
321: PropertyChangeEventJAI evt = new PropertyChangeEventJAI(
322: eventSource, propName, oldVal, newVal);
323: eventManager.firePropertyChange(evt);
324: }
325: }
326:
327: /**
328: * Overrides the method in <code>RenderableOp</code> to return the
329: * rendering-independent width of the image, as queried from the remote
330: * server.
331: *
332: * @return the image width as a float.
333: */
334: public float getWidth() {
335:
336: findRemoteCRIF();
337: Rectangle2D boundingBox = remoteCRIF.getBounds2D(serverName,
338: nodeSupport.getOperationName(), nodeSupport
339: .getParameterBlock());
340:
341: return (float) boundingBox.getWidth();
342: }
343:
344: /**
345: * Overrides the method in <code>RenderableOp</code> to return the
346: * rendering-independent height of the image, as queried from the remote
347: * server.
348: *
349: * @return the image height as a float.
350: */
351: public float getHeight() {
352:
353: findRemoteCRIF();
354: Rectangle2D boundingBox = remoteCRIF.getBounds2D(serverName,
355: nodeSupport.getOperationName(), nodeSupport
356: .getParameterBlock());
357:
358: return (float) boundingBox.getHeight();
359: }
360:
361: /**
362: * Overrides the method in <code>RenderableOp</code> to return the
363: * minimum X coordinate of the rendering-independent image data, as
364: * queried from the remote server.
365: */
366: public float getMinX() {
367:
368: findRemoteCRIF();
369: Rectangle2D boundingBox = remoteCRIF.getBounds2D(serverName,
370: nodeSupport.getOperationName(), nodeSupport
371: .getParameterBlock());
372:
373: return (float) boundingBox.getX();
374: }
375:
376: /**
377: * Overrides the method in <code>RenderableOp</code> to return the
378: * maximum X coordinate of the rendering-independent image data, as
379: * queried from the remote server.
380: */
381: public float getMinY() {
382:
383: findRemoteCRIF();
384: Rectangle2D boundingBox = remoteCRIF.getBounds2D(serverName,
385: nodeSupport.getOperationName(), nodeSupport
386: .getParameterBlock());
387:
388: return (float) boundingBox.getY();
389: }
390:
391: /**
392: * Overrides the <code>RenderableOp</code> method to return a
393: * <code>RemoteRenderedImage</code> that represents the remote rendering
394: * of this image using a given <code>RenderContext</code>. This is the
395: * most general way to obtain a rendering of a
396: * <code>RemoteRenderableOp</code>.
397: *
398: * <p> This method does not validate sources and parameters supplied
399: * in the <code>ParameterBlock</code> against the specification
400: * of the operation this node represents. It is the caller's
401: * responsibility to ensure that the data in the
402: * <code>ParameterBlock</code> are suitable for this operation.
403: * Otherwise, some kind of exception or error will occur.
404: *
405: * <p> <code>RemoteJAI.createRenderable()</code> is the method that does
406: * the validation. Therefore, it is strongly recommended that all
407: * <code>RemoteRenderableOp</code>s are created using
408: * <code>RemoteJAI.createRenderable()</code>.
409: *
410: * <p> The <code>RenderContext</code> may contain a <code>Shape</code>
411: * that represents the area-of-interest (aoi). If the aoi is specifed,
412: * it is still legal to return an image that's larger than this aoi.
413: * Therefore, by default, the aoi, if specified, is ignored at the
414: * rendering.
415: *
416: * <p> The <code>RenderingHints</code> in the <code>RenderContext</code>
417: * may contain negotiation preferences specified under the
418: * <code>KEY_NEGOTIATION_PREFERENCES</code> key. These preferences
419: * can be ignored by the rendering if it so chooses.
420: *
421: * @param renderContext the RenderContext to use to produce the rendering.
422: * @return a RemoteRenderedImage containing the rendered data.
423: */
424: public RenderedImage createRendering(RenderContext renderContext) {
425:
426: findRemoteCRIF();
427:
428: // Clone the original ParameterBlock; if the ParameterBlock
429: // contains RenderableImage sources, they will be replaced by
430: // RenderedImages.
431: ParameterBlock renderedPB = (ParameterBlock) nodeSupport
432: .getParameterBlock().clone();
433:
434: // If there are any hints set on the node, create a new
435: // RenderContext which merges them with those in the RenderContext
436: // passed in with the passed in hints taking precedence.
437: RenderContext rcIn = renderContext;
438: RenderingHints nodeHints = nodeSupport.getRenderingHints();
439: if (nodeHints != null) {
440: RenderingHints hints = renderContext.getRenderingHints();
441: RenderingHints mergedHints;
442: if (hints == null) {
443: mergedHints = nodeHints;
444: } else if (nodeHints == null || nodeHints.isEmpty()) {
445: mergedHints = hints;
446: } else {
447: mergedHints = new RenderingHints((Map) nodeHints);
448: mergedHints.add(hints);
449: }
450:
451: if (mergedHints != hints) {
452: rcIn = new RenderContext(renderContext.getTransform(),
453: renderContext.getAreaOfInterest(), mergedHints);
454: }
455: }
456:
457: // Get all sources - whether rendered or renderable.
458: Vector sources = nodeSupport.getParameterBlock().getSources();
459:
460: try {
461: if (sources != null) {
462: Vector renderedSources = new Vector();
463: for (int i = 0; i < sources.size(); i++) {
464:
465: RenderedImage rdrdImage = null;
466: Object source = sources.elementAt(i);
467: if (source instanceof RenderableImage) {
468: RenderContext rcOut = remoteCRIF
469: .mapRenderContext(serverName,
470: nodeSupport.getOperationName(),
471: i, renderContext, nodeSupport
472: .getParameterBlock(),
473: this );
474:
475: RenderableImage src = (RenderableImage) source;
476: rdrdImage = src.createRendering(rcOut);
477: } else if (source instanceof RenderedOp) {
478: rdrdImage = ((RenderedOp) source)
479: .getRendering();
480: } else if (source instanceof RenderedImage) {
481: rdrdImage = (RenderedImage) source;
482: }
483:
484: if (rdrdImage == null) {
485: return null;
486: }
487:
488: // Add this rendered image to the ParameterBlock's
489: // list of RenderedImages.
490: renderedSources.addElement(rdrdImage);
491: }
492:
493: if (renderedSources.size() > 0) {
494: renderedPB.setSources(renderedSources);
495: }
496: }
497:
498: RenderedImage rendering = remoteCRIF.create(serverName,
499: nodeSupport.getOperationName(), renderContext,
500: renderedPB);
501:
502: if (rendering instanceof RenderedOp) {
503: rendering = ((RenderedOp) rendering).getRendering();
504: }
505:
506: // Save a reference to the RMIServerProxy that is the link to
507: // the RenderableOp on the server. We don't want this to be
508: // garbage collected.
509: linkToRemoteOp = rendering;
510:
511: // Copy properties to the rendered node.
512: if (rendering != null
513: && rendering instanceof WritablePropertySource) {
514: String[] propertyNames = getPropertyNames();
515: if (propertyNames != null) {
516: WritablePropertySource wps = (WritablePropertySource) rendering;
517: for (int j = 0; j < propertyNames.length; j++) {
518: String name = propertyNames[j];
519: Object value = getProperty(name);
520: if (value != null
521: && value != java.awt.Image.UndefinedProperty) {
522: wps.setProperty(name, value);
523: }
524: }
525: }
526: }
527:
528: return rendering;
529: } catch (ArrayIndexOutOfBoundsException e) {
530: // This should never happen
531: return null;
532: }
533: }
534:
535: /** Use registry to find an appropriate RemoteCRIF */
536: private RemoteCRIF findRemoteCRIF() {
537:
538: if (remoteCRIF == null) {
539: // find the RemoteCRIF from the registry.
540: remoteCRIF = RemoteCRIFRegistry.get(nodeSupport
541: .getRegistry(), protocolName);
542:
543: if (remoteCRIF == null) {
544: throw new ImagingException(JaiI18N
545: .getString("RemoteRenderableOp0"));
546: }
547: }
548:
549: return remoteCRIF;
550: }
551:
552: /**
553: * Returns the amount of time between retries in milliseconds. If
554: * a value for the retry interval has been set previously by
555: * <code>setRetryInterval()</code>, the same value is returned, else
556: * the default retry interval as defined by
557: * <code>RemoteJAI.DEFAULT_RETRY_INTERVAL</code> is returned.
558: */
559: public int getRetryInterval() {
560:
561: RenderingHints rh = nodeSupport.getRenderingHints();
562: if (rh == null) {
563: return RemoteJAI.DEFAULT_RETRY_INTERVAL;
564: } else {
565: Integer i = (Integer) rh.get(JAI.KEY_RETRY_INTERVAL);
566: if (i == null)
567: return RemoteJAI.DEFAULT_RETRY_INTERVAL;
568: else
569: return i.intValue();
570: }
571: }
572:
573: /**
574: * Sets the amount of time between retries in milliseconds.
575: *
576: * <p> This new value for retry interval will be stored and will
577: * be passed as <code>RenderingHints</code> as part
578: * of the <code>RenderContext</code> used to create the rendering. The
579: * <code>RenderingHints</code> in the <code>RenderContext</code> will
580: * contain this information under the
581: * <code>KEY_RETRY_INTERVAL</code> key. If the
582: * <code>RenderingHints</code> in the <code>RenderContext</code> already
583: * contains a retry interval value specified by the user, that will
584: * take preference over the one stored in this class.
585: *
586: * @param retryInterval The amount of time (in milliseconds) to wait
587: * between retries.
588: * @throws IllegalArgumentException if retryInterval is negative.
589: */
590: public void setRetryInterval(int retryInterval) {
591:
592: if (retryInterval < 0)
593: throw new IllegalArgumentException(JaiI18N
594: .getString("Generic3"));
595:
596: RenderingHints rh = nodeSupport.getRenderingHints();
597: if (rh == null) {
598: RenderingHints hints = new RenderingHints(null);
599: nodeSupport.setRenderingHints(hints);
600: }
601:
602: nodeSupport.getRenderingHints().put(JAI.KEY_RETRY_INTERVAL,
603: new Integer(retryInterval));
604: }
605:
606: /**
607: * Returns the number of retries. If a value for the number of retries
608: * has been set previously by <code>setNumRetries()</code>, the same
609: * value is returned, else the default number of retries as defined by
610: * <code>RemoteJAI.DEFAULT_NUM_RETRIES</code> is returned.
611: */
612: public int getNumRetries() {
613:
614: RenderingHints rh = nodeSupport.getRenderingHints();
615: if (rh == null) {
616: return RemoteJAI.DEFAULT_NUM_RETRIES;
617: } else {
618: Integer i = (Integer) rh.get(JAI.KEY_NUM_RETRIES);
619: if (i == null)
620: return RemoteJAI.DEFAULT_NUM_RETRIES;
621: else
622: return i.intValue();
623: }
624: }
625:
626: /**
627: * Sets the number of retries.
628: *
629: * <p> This new value for number of retries will be stored and will
630: * be passed as <code>RenderingHints</code> as part
631: * of the <code>RenderContext</code> used to create the rendering. The
632: * <code>RenderingHints</code> in the <code>RenderContext</code> will
633: * contain this information under the
634: * <code>KEY_NUM_RETRIES</code> key. If the
635: * <code>RenderingHints</code> in the <code>RenderContext</code> already
636: * contains a number of retries value specified by the user, that will
637: * take preference over the one stored in this class.
638: *
639: * @param numRetries The number of times an operation should be retried
640: * in case of a network error.
641: * @throws IllegalArgumentException if numRetries is negative.
642: */
643: public void setNumRetries(int numRetries) {
644:
645: if (numRetries < 0)
646: throw new IllegalArgumentException(JaiI18N
647: .getString("Generic4"));
648:
649: RenderingHints rh = nodeSupport.getRenderingHints();
650: if (rh == null) {
651: RenderingHints hints = new RenderingHints(null);
652: nodeSupport.setRenderingHints(hints);
653: }
654:
655: nodeSupport.getRenderingHints().put(JAI.KEY_NUM_RETRIES,
656: new Integer(numRetries));
657: }
658:
659: /**
660: * Returns the current negotiation preferences or null, if none were
661: * set previously.
662: */
663: public NegotiableCapabilitySet getNegotiationPreferences() {
664:
665: RenderingHints rh = nodeSupport.getRenderingHints();
666:
667: NegotiableCapabilitySet ncs = rh == null ? null
668: : (NegotiableCapabilitySet) rh
669: .get(JAI.KEY_NEGOTIATION_PREFERENCES);
670: return ncs;
671: }
672:
673: /**
674: * Sets the preferences to be used in the client-server
675: * communication. These preferences are utilized in the negotiation
676: * process. Note that preferences for more than one category can be
677: * specified using this method. Also each preference can be a list
678: * of values in decreasing order of preference, each value specified
679: * as a <code>NegotiableCapability</code>. The
680: * <code>NegotiableCapability</code> first (for a particular category)
681: * in this list is given highest priority in the negotiation process
682: * (for that category).
683: *
684: * <p> It may be noted that this method allows for multiple negotiation
685: * cycles by allowing negotiation preferences to be set
686: * multiple times. Every time this method is called, the new preferences
687: * specified will be stored, a negotiation with these new preferences
688: * will be initiated and the results stored. These new preferences which
689: * have been stored will be passed as <code>RenderingHints</code> as part
690: * of the <code>RenderContext</code> used to create the rendering. The
691: * <code>RenderingHints</code> in the <code>RenderContext</code> will
692: * contain this information under the
693: * <code>KEY_NEGOTIATION_PREFERENCES</code> key. If the
694: * <code>RenderingHints</code> in the <code>RenderContext</code> already
695: * contains negotiation preferences specified by the user, the user
696: * specified negotiation preferences will take preference over the ones
697: * stored in this class.
698: *
699: * <p> If preferences to be set are null, the negotiation will become
700: * a two-way negotiation between the client and server capabilities.
701: *
702: * @param preferences The preferences to be used in the negotiation
703: * process.
704: */
705: public void setNegotiationPreferences(
706: NegotiableCapabilitySet preferences) {
707:
708: RenderingHints rh = nodeSupport.getRenderingHints();
709:
710: // If there are preferences to set
711: if (preferences != null) {
712:
713: // Check whether RenderingHints exists, if not, create it.
714: if (rh == null) {
715: RenderingHints hints = new RenderingHints(null);
716: nodeSupport.setRenderingHints(hints);
717: }
718:
719: // Set the provided preferences into the RenderingHints
720: nodeSupport.getRenderingHints().put(
721: JAI.KEY_NEGOTIATION_PREFERENCES, preferences);
722: } else { // Preferences is null
723: // Remove any previous values set for negotiation preferences
724: if (rh != null) {
725: rh.remove(JAI.KEY_NEGOTIATION_PREFERENCES);
726: }
727: }
728:
729: negotiated = negotiate(preferences);
730: }
731:
732: private NegotiableCapabilitySet negotiate(
733: NegotiableCapabilitySet prefs) {
734:
735: OperationRegistry registry = nodeSupport.getRegistry();
736:
737: NegotiableCapabilitySet serverCap = null;
738:
739: // Get the RemoteDescriptor for protocolName
740: RemoteDescriptor descriptor = (RemoteDescriptor) registry
741: .getDescriptor(RemoteDescriptor.class, protocolName);
742:
743: if (descriptor == null) {
744: Object[] msgArg0 = { new String(protocolName) };
745: MessageFormat formatter = new MessageFormat("");
746: formatter.setLocale(Locale.getDefault());
747: formatter.applyPattern(JaiI18N.getString("RemoteJAI16"));
748: throw new RuntimeException(formatter.format(msgArg0));
749: }
750:
751: int count = 0;
752: int numRetries = getNumRetries();
753: int retryInterval = getRetryInterval();
754:
755: Exception rieSave = null;
756: while (count++ < numRetries) {
757: try {
758: serverCap = descriptor
759: .getServerCapabilities(serverName);
760: break;
761: } catch (RemoteImagingException rie) {
762: // Print that an Exception occured
763: System.err.println(JaiI18N.getString("RemoteJAI24"));
764: rieSave = rie;
765: // Sleep for retryInterval milliseconds
766: try {
767: Thread.sleep(retryInterval);
768: } catch (InterruptedException ie) {
769: // throw new RuntimeException(ie.toString());
770: sendExceptionToListener(JaiI18N
771: .getString("Generic5"),
772: new ImagingException(JaiI18N
773: .getString("Generic5"), ie));
774: }
775: }
776: }
777:
778: if (serverCap == null && count > numRetries) {
779: sendExceptionToListener(JaiI18N.getString("RemoteJAI18"),
780: rieSave);
781: // throw new RemoteImagingException(JaiI18N.getString("RemoteJAI18")
782: // + "\n" + rieSave.getMessage());
783: }
784:
785: RemoteRIF rrif = (RemoteRIF) registry.getFactory(
786: "remoteRenderable", protocolName);
787:
788: return RemoteJAI.negotiate(prefs, serverCap, rrif
789: .getClientCapabilities());
790: }
791:
792: /**
793: * Returns the results of the negotiation between the client and server
794: * capabilities according to the preferences set via the
795: * <code>setNegotiationPreferences()</code> method. This will return null
796: * if no negotiation preferences were set, and no negotiation was
797: * performed, or if the negotiation failed.
798: */
799: public NegotiableCapabilitySet getNegotiatedValues()
800: throws RemoteImagingException {
801: return negotiated;
802: }
803:
804: /**
805: * Returns the results of the negotiation between the client and server
806: * capabilities for the given catgory according to the preferences set
807: * via the <code>setNegotiationPreferences()</code> method. This will
808: * return null if no negotiation preferences were set, and no
809: * negotiation was performed, or if the negotiation failed.
810: *
811: * @param category The category to return negotiated results for.
812: */
813: public NegotiableCapability getNegotiatedValues(String category)
814: throws RemoteImagingException {
815: if (negotiated != null)
816: return negotiated.getNegotiatedValue(category);
817: return null;
818: }
819:
820: void sendExceptionToListener(String message, Exception e) {
821: ImagingListener listener = (ImagingListener) getRenderingHints()
822: .get(JAI.KEY_IMAGING_LISTENER);
823:
824: listener.errorOccurred(message, e, this , false);
825: }
826: }
|