001: /*
002: * $RCSfile: JAIRMIDescriptor.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.1 $
009: * $Date: 2005/02/11 04:57:50 $
010: * $State: Exp $
011: */package javax.media.jai.remote;
012:
013: import java.awt.RenderingHints;
014: import java.awt.Rectangle;
015: import java.awt.image.renderable.ParameterBlock;
016: import java.net.URL;
017: import java.net.InetAddress;
018: import java.rmi.Naming;
019: import java.rmi.RemoteException;
020: import java.text.MessageFormat;
021: import java.util.Iterator;
022: import java.util.Enumeration;
023: import java.util.Hashtable;
024: import java.util.Locale;
025: import java.util.List;
026: import java.util.Vector;
027: import javax.media.jai.JAI;
028: import javax.media.jai.OperationDescriptor;
029: import javax.media.jai.OperationNode;
030: import javax.media.jai.RenderedOp;
031: import javax.media.jai.ParameterListDescriptor;
032: import javax.media.jai.util.CaselessStringKey;
033: import javax.media.jai.util.ImagingException;
034: import javax.media.jai.util.ImagingListener;
035: import com.sun.media.jai.rmi.ImageServer;
036: import com.sun.media.jai.rmi.RMIServerProxy;
037: import com.sun.media.jai.rmi.JAIRMIUtil;
038: import com.sun.media.jai.util.ImageUtil;
039:
040: /**
041: * This class describes the "jairmi" remote imaging protocol. This protocol
042: * assumes that both the client and the server are running JAI. The
043: * communication between the client and the server takes place using
044: * the Remote Method Invocation (RMI) mechanism.
045: *
046: * <p> In order to locate the "jairmi" server, a RMI registry must be
047: * running on this server, and the "jairmi" server must have registered
048: * itself with this RMI registry by binding itself under the
049: * <code>IMAGE_SERVER_BIND_NAME</code> <code>String</code>. The RMI
050: * registry is a simple remote object name service that allows remote
051: * clients to get a reference to a remote object by name.
052: *
053: * <p> The "jairmi" protocol expects the <code>String</code> that
054: * represents the server to be a <code>URL</code> formatted
055: * <code>String</code> of the form:
056: *
057: * <pre>
058: * //host:port
059: * </pre>
060: *
061: * where <code>host</code> is the name, or IP address of the "jairmi"
062: * remote imaging server, and <code>port</code> is the port number
063: * where a rmiregistry is running on the same host. A protocol like
064: * "rmi:" does not need to be included in this URL formatted
065: * <code>String</code>. If the serverName
066: * <code>String</code> is null, the local host is used as a default.
067: * If the port is not included in the serverName <code>String</code>, it
068: * defaults to the well-known port for rmiregistry, 1099.
069: *
070: * <p> If the serverName supplied to any "jairmi" protocol implementing
071: * class's method is null, then the local host will be used instead.
072: *
073: * <p> The default "jairmi" server provided with JAI is
074: * <code>com.sun.media.jai.rmi.JAIRMIRemoteServer</code>. This server
075: * can be run in the following manner, after starting a rmiregistry on
076: * the host where the server will be run:
077: *
078: * <pre>
079: * java -Djava.rmi.server.codebase="file:$JAI/lib/jai_core.jar file:$JAI/lib/jai_codec.jar" -Djava.rmi.server.useCodebaseOnly=false -Djava.security.policy=file:$JAI/policy com.sun.media.jai.rmi.JAIRMIImageServer
080: * </pre>
081: *
082: * where $JAI refers to the directory where JAI is installed. This server
083: * binds itself with the running rmiregistry under the
084: * <code>IMAGE_SERVER_BIND_NAME</code> <code>String</code> bind name, and
085: * can be used to serve "jairmi" requests. The policy file specified
086: * above needs to be created by the user. Information on policy
087: * files and permissions can be found at
088: * <p>http://java.sun.com/j2se/1.3/docs/guide/security/PolicyFiles.html
089: * <p>http://java.sun.com/j2se/1.3/docs/guide/security/permissions.html
090: *
091: * <p> The JAI instance used by the "jairmi" remote imaging server can be
092: * configured by providing an implementation of the
093: * <code>com.sun.media.jai.remote.JAIServerConfigurationSpi</code> interface
094: * on the <code>CLASSPATH</code> when starting the server.
095: * For more details, please refer to
096: * {@link com.sun.media.jai.remote.JAIServerConfigurationSpi}
097: *
098: * <p> The "jairmi" remote imaging server supports the following
099: * configurable parameters whose values can be specified on the command
100: * line when starting the server :
101: *
102: * <code>
103: * -host <string> The server name or server IP address
104: * -port <integer> The port that rmiregistry is running on
105: * -rmiRegistryPort <integer> Same as -port option
106: * -serverPort <integer> The port that the server should listen on,
107: * for connections from clients
108: * -cacheMemCapacity <long> The memory capacity in bytes.
109: * -cacheMemThreshold <float> The memory threshold, which is the
110: * fractional amount of cache memory to
111: * retain during tile removal
112: * -disableDefaultCache Disable use of default tile cache.
113: * -schedulerParallelism <integer> The degree of parallelism of the
114: * default TileScheduler
115: * -schedulerPrefetchParallelism <integer> The degree of parallelism
116: * of the default TileScheduler for tile prefetching
117: * -schedulerPriority <integer> The priority of tile scheduling for
118: * the default TileScheduler
119: * -schedulerPrefetchPriority <integer> The priority of tile prefetch
120: * scheduling for the default TileScheduler
121: * -defaultTileSize <integer>x<integer> The default tile dimensions in
122: * the form <xSize>x<ySize>
123: * -defaultRenderingSize <integer>x<integer> The default size to render
124: * a RenderableImage to, in the form <xSize>x<ySize>
125: * -serializeDeepCopy <boolean> Whether a deep copy of the image data
126: * should be used when serializing images
127: * -tileCodecFormat <string> The default format to be used for tile
128: * serialization via TileCodecs
129: * -retryInterval <integer> The retry interval value to be used for
130: * dealing with network errors during remote imaging
131: * -numRetries <integer> The number of retries to be used for dealing
132: * with network errors during remote imaging
133: * </code>
134: *
135: * <p> It should be noted that if a parameter
136: * was set via JAIServerConfigurationSpi, and the command line option for
137: * the same parameter specifies a different value, then the command line
138: * specified parameter value will be honored. That is to say that the
139: * JAIServerConfigurationSpi specified configuration happens first, followed
140: * by command line parameter configuration, and the last configuration to
141: * be applied overwrites all previous settings.
142:
143: * @since JAI 1.1
144: */
145: public class JAIRMIDescriptor extends RemoteDescriptorImpl {
146:
147: /**
148: * The bind name for the remote "jairmi" server. This is also the
149: * name that the "jairmi" client looks for when trying to locate
150: * a "jairmi" server.
151: */
152: public static final String IMAGE_SERVER_BIND_NAME = "JAIRMIRemoteServer1.1";
153:
154: // A MessageFormat object to format the error strings.
155: private MessageFormat formatter;
156:
157: /**
158: * Creates a <code>JAIRMIDescriptor</code>.
159: */
160: public JAIRMIDescriptor() throws java.net.MalformedURLException {
161: super (
162: "jairmi",
163: new URL(
164: "http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/javax/media/jai/remote/JAIRMIDescriptor.html"));
165:
166: formatter = new MessageFormat("");
167: formatter.setLocale(Locale.getDefault());
168: }
169:
170: /**
171: * Returns the list of <code>OperationDescriptor</code>s that describe
172: * the operations supported by the server. It is the
173: * implementing class's responsibility to extract this information from
174: * either the server or from its own knowledge of the remote imaging
175: * protocol. The "jairmi" protocol gets this information from the server.
176: *
177: * <p> If the supplied serverName argument is null, then the local
178: * host will be used instead.
179: *
180: * @param serverName The <code>String</code> identifying the server.
181: */
182: public OperationDescriptor[] getServerSupportedOperationList(
183: String serverName) throws RemoteImagingException {
184:
185: List odList = null;
186: try {
187: odList = getImageServer(serverName)
188: .getOperationDescriptors();
189: } catch (Exception e) {
190: sendExceptionToListener(JaiI18N
191: .getString("JAIRMIDescriptor12"),
192: new RemoteImagingException(JaiI18N
193: .getString("JAIRMIDescriptor12"), e));
194: // throw new RemoteImagingException(ImageUtil.getStackTraceString(e));
195: }
196:
197: OperationDescriptor[] od = new OperationDescriptor[odList
198: .size()];
199: int count = 0;
200: for (Iterator i = odList.iterator(); i.hasNext();) {
201: od[count++] = (OperationDescriptor) i.next();
202: }
203:
204: return od;
205: }
206:
207: private ImageServer getImageServer(String serverName) {
208:
209: if (serverName == null) {
210: try {
211: serverName = InetAddress.getLocalHost()
212: .getHostAddress();
213: } catch (Exception e) {
214: sendExceptionToListener(JaiI18N
215: .getString("JAIRMIDescriptor13"),
216: new ImagingException(JaiI18N
217: .getString("JAIRMIDescriptor13"), e));
218: // throw new RuntimeException(e.getMessage());
219: }
220: }
221:
222: // Derive the service name.
223: String serviceName = new String("rmi://" + serverName + "/"
224: + IMAGE_SERVER_BIND_NAME);
225:
226: ImageServer imageServer = null;
227: // Look up the remote object.
228: try {
229: imageServer = (ImageServer) Naming.lookup(serviceName);
230: } catch (Exception e) {
231: sendExceptionToListener(JaiI18N
232: .getString("JAIRMIDescriptor14"),
233: new RemoteImagingException(JaiI18N
234: .getString("JAIRMIDescriptor14"), e));
235: // throw new RemoteImagingException(ImageUtil.getStackTraceString(e));
236: }
237:
238: return imageServer;
239: }
240:
241: /**
242: * Returns the set of capabilites supported by the server. It is the
243: * implementing class's responsibility to extract this information from
244: * either the server or from its own knowledge of the remote imaging
245: * protocol. The "jairmi" protocol gets this information from the server.
246: *
247: * <p> If the supplied serverName argument is null, then the local
248: * host will be used instead.
249: *
250: * @param serverName The <code>String</code> identifying the server.
251: */
252: public NegotiableCapabilitySet getServerCapabilities(
253: String serverName) throws RemoteImagingException {
254:
255: NegotiableCapabilitySet serverCapabilities = null;
256: try {
257: serverCapabilities = getImageServer(serverName)
258: .getServerCapabilities();
259: } catch (Exception e) {
260: sendExceptionToListener(JaiI18N
261: .getString("JAIRMIDescriptor15"),
262: new RemoteImagingException(JaiI18N
263: .getString("JAIRMIDescriptor15"), e));
264: // throw new RemoteImagingException(ImageUtil.getStackTraceString(e));
265: }
266:
267: return serverCapabilities;
268: }
269:
270: /**
271: * Calculates the region over which two distinct remote renderings
272: * of an operation may be expected to differ. The operation is
273: * represented by the <code>OperationNode</code> argument to this
274: * method. The <code>String</code> that identifies the operation
275: * can be retrieved via the <code>OperationNode</code>'s
276: * <code>getOperationName()</code> method.
277: *
278: * <p> The class of the returned object will vary as a function of
279: * the nature of the operation. For rendered and renderable two-
280: * dimensional images this should be an instance of a class which
281: * implements <code>java.awt.Shape</code>.
282: *
283: * @param registryModeName The name of the mode.
284: * @param oldServerName The previous server name.
285: * @param oldParamBlock The previous sources and parameters.
286: * @param oldHints The previous hints.
287: * @param newServerName The current server name.
288: * @param newParamBlock The current sources and parameters.
289: * @param newHints The current hints.
290: * @param node The affected node in the processing chain.
291: *
292: * @return The region over which the data of two renderings of this
293: * operation may be expected to be invalid or <code>null</code>
294: * if there is no common region of validity. If an empty
295: * <code>java.awt.Shape</code> is returned, this indicates
296: * that all pixels within the bounds of the old rendering
297: * remain valid.
298: *
299: * @throws IllegalArgumentException if <code>registryModeName</code>
300: * is <code>null</code> or if the operation requires either
301: * sources or parameters and either <code>oldParamBlock</code>
302: * or <code>newParamBlock</code> is <code>null</code>.
303: * @throws IllegalArgumentException if there is no OperationDescriptor
304: * for the specified operationName on any one or both of the
305: * servers identified by <code>oldServerName</code> and
306: * <code>newServerName</code>, or if the number of sources or
307: * the name, number and <code>Class</code> of the operation's
308: * parameters is not the same on both the servers.
309: * @throws IllegalArgumentException if <code>oldParamBlock</code> or
310: * <code>newParamBlock</code> do not contain sufficient sources
311: * or parameters for the operation in question.
312: */
313: public Object getInvalidRegion(String registryModeName,
314: String oldServerName, ParameterBlock oldParamBlock,
315: RenderingHints oldHints, String newServerName,
316: ParameterBlock newParamBlock, RenderingHints newHints,
317: OperationNode node) throws RemoteImagingException {
318:
319: if (registryModeName == null)
320: throw new IllegalArgumentException(JaiI18N
321: .getString("JAIRMIDescriptor11"));
322:
323: String operationName = node.getOperationName();
324: OperationDescriptor oldDescs[] = getServerSupportedOperationList(oldServerName);
325: OperationDescriptor oldOD = getOperationDescriptor(oldDescs,
326: operationName);
327:
328: if (oldOD == null)
329: throw new IllegalArgumentException(JaiI18N
330: .getString("JAIRMIDescriptor1"));
331:
332: int numSources = oldOD.getNumSources();
333:
334: // If the supplied registryModeName is "remoteRendered" or
335: // "remoteRenderable", in order to get the OperationDescriptor's
336: // ParameterListDescriptor, we need to actually use the "rendered"
337: // or "renderable" mode respectively.
338: ParameterListDescriptor oldPLD = null;
339: if (registryModeName.equalsIgnoreCase("remoteRendered")) {
340: oldPLD = oldOD.getParameterListDescriptor("rendered");
341: } else if (registryModeName
342: .equalsIgnoreCase("remoteRenderable")) {
343: oldPLD = oldOD.getParameterListDescriptor("renderable");
344: } else {
345: oldPLD = oldOD.getParameterListDescriptor(registryModeName);
346: }
347:
348: int numParams = oldPLD.getNumParameters();
349:
350: // If the serverNames are same, nothing to be done for that
351: if (oldServerName != newServerName) {
352:
353: // Check whether they both support the supplied operation name
354:
355: OperationDescriptor newDescs[] = getServerSupportedOperationList(newServerName);
356: OperationDescriptor newOD;
357:
358: if ((newOD = getOperationDescriptor(newDescs, operationName)) == null)
359: throw new IllegalArgumentException(JaiI18N
360: .getString("JAIRMIDescriptor2"));
361:
362: // Check the OperationDescriptor equivalence
363:
364: // Sources
365: if (numSources != newOD.getNumSources())
366: throw new IllegalArgumentException(JaiI18N
367: .getString("JAIRMIDescriptor3"));
368:
369: // Parameters
370: ParameterListDescriptor newPLD = newOD
371: .getParameterListDescriptor(registryModeName);
372:
373: if (numParams != newPLD.getNumParameters())
374: throw new IllegalArgumentException(JaiI18N
375: .getString("JAIRMIDescriptor4"));
376:
377: // Param names
378: String oldParamNames[] = oldPLD.getParamNames();
379: if (oldParamNames == null)
380: oldParamNames = new String[0];
381: String newParamNames[] = newPLD.getParamNames();
382: if (newParamNames == null)
383: newParamNames = new String[0];
384:
385: Hashtable oldHash = hashNames(oldParamNames);
386: Hashtable newHash = hashNames(newParamNames);
387:
388: // The same names should be present in both in the same order.
389: if (containsAll(oldHash, newHash) == false)
390: throw new IllegalArgumentException(JaiI18N
391: .getString("JAIRMIDescriptor8"));
392:
393: // Param class types
394: Class this ParamClasses[] = oldPLD.getParamClasses();
395: Class otherParamClasses[] = newPLD.getParamClasses();
396: for (int i = 0; i < oldParamNames.length; i++) {
397: if (this ParamClasses[i] != otherParamClasses[getIndex(
398: newHash, oldParamNames[i])])
399: throw new IllegalArgumentException(JaiI18N
400: .getString("JAIRMIDescriptor9"));
401: }
402:
403: // XXX Could be made more efficient by returning the area that
404: // might be valid if both the servers support the same operations,
405: // current implementation just returns null.
406: return null;
407: }
408:
409: // Perform the other checks listed in the method spec
410:
411: // Neither the old and the new ParamBlock should be null, if
412: // the operation requires some sources or some parameters.
413: if ((registryModeName == null)
414: || ((numSources > 0 || numParams > 0) && (oldParamBlock == null || newParamBlock == null))) {
415: throw new IllegalArgumentException(JaiI18N
416: .getString("JAIRMIDescriptor5"));
417: }
418:
419: // Both the old and new ParameterBlock should contain the
420: // required number of sources.
421: if ((numSources > 0)
422: && (oldParamBlock.getNumSources() != numSources || newParamBlock
423: .getNumSources() != numSources)) {
424: Object[] msgArg0 = { operationName, new Integer(numParams) };
425: formatter.applyPattern(JaiI18N
426: .getString("JAIRMIDescriptor6"));
427: throw new IllegalArgumentException(formatter
428: .format(msgArg0));
429: }
430:
431: // Both the old and new ParameterBlock should contain the
432: // required number of parameters.
433: if ((numParams > 0)
434: && (oldParamBlock.getNumParameters() != numParams || newParamBlock
435: .getNumParameters() != numParams)) {
436: Object[] msgArg0 = { operationName, new Integer(numParams) };
437: formatter.applyPattern(JaiI18N
438: .getString("JAIRMIDescriptor7"));
439: throw new IllegalArgumentException(formatter
440: .format(msgArg0));
441: }
442:
443: // Find the id that refers to the corresponding RenderedOp on the
444: // server
445: RenderedOp op = (RenderedOp) node;
446: Object rendering = op.getRendering();
447: Long id = null;
448: if (rendering instanceof RMIServerProxy) {
449: id = ((RMIServerProxy) rendering).getRMIID();
450: } else {
451: throw new RuntimeException(JaiI18N
452: .getString("JAIRMIDescriptor10"));
453: }
454:
455: // Check whether any of the sources of this operation are on
456: // remote "jairmi" servers and if so, replace the source with
457: // it's id.
458:
459: boolean samePBs = false;
460: if (oldParamBlock == newParamBlock)
461: samePBs = true;
462:
463: Vector oldSources = oldParamBlock.getSources();
464: oldParamBlock.removeSources();
465: // Ensure that any images which are parameters are replaced byte
466: // suitable representations
467: JAIRMIUtil.checkClientParameters(oldParamBlock, oldServerName);
468: oldParamBlock.setSources(JAIRMIUtil.replaceSourcesWithId(
469: oldSources, oldServerName));
470:
471: if (samePBs) {
472: newParamBlock = oldParamBlock;
473: } else {
474: Vector newSources = newParamBlock.getSources();
475: newParamBlock.removeSources();
476: // Ensure that any images which are parameters are replaced byte
477: // suitable representations
478: JAIRMIUtil.checkClientParameters(newParamBlock,
479: oldServerName);
480: newParamBlock.setSources(JAIRMIUtil.replaceSourcesWithId(
481: newSources, oldServerName));
482:
483: }
484:
485: // Serialize the old and new RenderingHints
486: SerializableState oldRHS = SerializerFactory.getState(oldHints,
487: null);
488: SerializableState newRHS = SerializerFactory.getState(newHints,
489: null);
490:
491: SerializableState shapeState = null;
492: try {
493: shapeState = getImageServer(oldServerName)
494: .getInvalidRegion(id, oldParamBlock, oldRHS,
495: newParamBlock, newRHS);
496: } catch (Exception e) {
497: sendExceptionToListener(JaiI18N
498: .getString("JAIRMIDescriptor16"),
499: new RemoteImagingException(JaiI18N
500: .getString("JAIRMIDescriptor16"), e));
501: // throw new RemoteImagingException(ImageUtil.getStackTraceString(e));
502: }
503:
504: return shapeState.getObject();
505: }
506:
507: private Hashtable hashNames(String paramNames[]) {
508:
509: Hashtable h = new Hashtable();
510: if (paramNames != null) {
511: for (int i = 0; i < paramNames.length; i++) {
512: h.put(new CaselessStringKey(paramNames[i]),
513: new Integer(i));
514: }
515: }
516:
517: return h;
518: }
519:
520: private int getIndex(Hashtable h, String s) {
521: return ((Integer) h.get(new CaselessStringKey(s))).intValue();
522: }
523:
524: // A case insensitive containsAll for Hashtables containing Strings
525: private boolean containsAll(Hashtable this Hash, Hashtable otherHash) {
526:
527: CaselessStringKey this NameKey;
528: for (Enumeration i = this Hash.keys(); i.hasMoreElements();) {
529: this NameKey = (CaselessStringKey) i.nextElement();
530: if (otherHash.containsKey(this NameKey) == false)
531: return false;
532: }
533:
534: return true;
535: }
536:
537: private OperationDescriptor getOperationDescriptor(
538: OperationDescriptor descriptors[], String operationName) {
539:
540: OperationDescriptor od;
541: for (int i = 0; i < descriptors.length; i++) {
542: od = descriptors[i];
543: if (od.getName().equalsIgnoreCase(operationName))
544: return od;
545: }
546:
547: return null;
548: }
549:
550: void sendExceptionToListener(String message, Exception e) {
551: ImagingListener listener = JAI.getDefaultInstance()
552: .getImagingListener();
553: listener.errorOccurred(message, e, this , false);
554: }
555: }
|