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  
19  package org.apache.river.start;
20  
21  import java.io.BufferedInputStream;
22  import java.io.BufferedOutputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.InvalidObjectException;
28  import java.io.ObjectInputStream;
29  import java.io.ObjectOutputStream;
30  import java.io.ObjectStreamException;
31  import java.io.Serializable;
32  import java.rmi.MarshalledObject;
33  import java.rmi.activation.ActivationException;
34  import java.rmi.activation.ActivationGroupDesc;
35  import java.rmi.activation.ActivationGroupDesc.CommandEnvironment;
36  import java.rmi.activation.ActivationGroupID;
37  import java.rmi.activation.ActivationSystem;
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.Properties;
41  import java.util.logging.Logger;
42  import net.jini.config.Configuration;
43  import net.jini.io.MarshalledInstance;
44  import org.apache.river.api.io.AtomicSerial;
45  import org.apache.river.api.io.AtomicSerial.GetArg;
46  import org.apache.river.api.io.Valid;
47  
48  /**
49   * Class used to create a shared activation group. 
50   * Clients construct this object with the details
51   * of the activation group to be launched, then call <code>create</code>
52   * to register the activation system group with the activation system
53   * <P>
54   * This class, in conjunction with the {@link ActivateWrapper} class,
55   * creates an activation group suitable for hosting
56   * multiple service objects, with each object 
57   * maintaining a distinct codebase and policy. 
58   *
59   * @author Sun Microsystems, Inc.
60   *
61   * @since 2.0
62   */
63  @AtomicSerial
64  public class SharedActivationGroupDescriptor 
65      implements ServiceDescriptor, Serializable
66  {
67      private static final long serialVersionUID = 1L;
68  
69      // Required Args
70      /**
71       * @serial <code>String</code> representing VM policy filename or URL
72       */
73      private final String policy;
74      
75      /**
76       * @serial <code>String</code> representing the class path of the shared VM
77       *     classes
78       */
79      private final String classpath;
80      
81      /**
82       * @serial <code>String</code> representing the location where group identifier 
83       *     information will be persisted
84       */
85      private final String log;
86      
87      // Optional Args
88      /**
89       * @serial <code>String</code> representing the VM command to use
90       */
91      private final String serverCommand;
92      
93      /**
94       * @serial <code>String[]</code> representing array of command line 
95       *     options to pass to the VM's command line 
96       */
97      private final String[] serverOptions;
98      
99      /**
100      * @serial <code>Properties</code> representing propperties to pass
101      *     to the VM's command line
102      */
103     private final Properties serverProperties;
104     
105     /**
106      * @serial <code>String</code> representing host name of the desired 
107      *     activation system
108      */
109     private final String host;
110     
111     /**
112      * @serial <code>int</code> representing port of the desired activation 
113      *     system
114      */
115     private final int port;
116     
117     private static final String GROUP_COOKIE_FILE = "cookie";
118     
119     private static final Logger logger = ServiceStarter.logger;
120     
121 
122     /**
123      * Trivial constructor. Simply calls the other overloaded constructor
124      * with the <code>host</code> and <code>port</code> parameters set to 
125      * <code>null</code> and 0, respectively.
126      * 
127      */
128     public SharedActivationGroupDescriptor(
129 	//Required Args
130 	String policy, String classpath, String log,
131 	//Optional Args
132 	String serverCommand, String[] serverOptions,
133 	String[] serverProperties)
134     {
135 	this(policy, classpath, log, serverCommand, serverOptions,
136 	     serverProperties, null, 
137 	     ServiceStarter.getActivationSystemPort());
138     }
139 
140     /**
141      * Trivial constructor. Simply assigns given parameters to 
142      * their associated, internal fields.
143      * 
144      * @param policy location of VM policy filename or URL
145      * @param classpath location where shared VM
146      *     classes can be found. Classpath components must be separated 
147      *     by path separators.
148      * @param log location where group identifier information will be persisted
149      * @param serverCommand VM command to use
150      * @param serverOptions array of command line options to pass on the VM's
151      *     command line
152      * @param serverProperties array of property/value string pairs to 
153      *     pass on the VMs command line (as in -D&lt;property&gt;=value). This
154      *     array must have an even number of elements.
155      * @param host hostname of desired activation system. If <code>null</code>,
156      *     defaults to the localhost.  
157      * @param port port of desired activation system. If value is &lt;= 0, then
158      *     defaults to  
159      *     {@link java.rmi.activation.ActivationSystem#SYSTEM_PORT 
160      *     ActivationSystem.SYSTEM_PORT}.
161      */
162     public SharedActivationGroupDescriptor(
163 	//Required Args
164 	String policy, String classpath, String log,
165 	//Optional Args
166 	String serverCommand, String[] serverOptions, 
167 	String[] serverProperties, String host, int port)
168     {
169 	if (policy == null || classpath == null || log == null) {
170             throw new NullPointerException(
171 		"Policy, classpath, or log cannot be null");
172 	}
173 	this.policy = policy;
174 	this.classpath = classpath;
175 	this.log = log;
176 	this.serverCommand = serverCommand;
177 	this.serverOptions = 
178 	    customizeSharedGroupOptions(classpath, serverOptions);
179 	Properties props = 
180 	    convertToProperties(serverProperties);
181 	this.serverProperties = 
182 	    customizeSharedGroupProperties(policy, props);
183 	this.host = (host == null) ? "" : host;
184 	if (port <= 0) {
185 	    this.port = ServiceStarter.getActivationSystemPort(); 
186 	} else {
187 	    this.port = port;
188 	}
189     }
190     
191     SharedActivationGroupDescriptor(GetArg arg) 
192 	    throws IOException, ClassNotFoundException{
193 	this(
194 		Valid.notNull(arg.get("policy", null, String.class), "Policy cannot be null"),
195 		Valid.notNull(arg.get("classpath", null, String.class), "Classpath cannot be null"),
196 		Valid.notNull(arg.get("log",null, String.class), "log cannot be null"),
197 		arg.get("serverCommand", null, String.class),
198 		arg.get("serverOptions", null, String[].class),
199 		arg.get("serverProperties", null, String[].class),
200 		arg.get("host", null, String.class),
201 		arg.get("port", 0)
202 	);
203     }
204  
205     /**
206      * Policy accessor method.
207      *
208      * @return the policy location associated with this service descriptor.
209      */
210     final public String getPolicy() { return policy; }
211     
212     /**
213      * Classpath accessor method.
214      *
215      * @return classpath associated with this service descriptor.
216      */
217     final public String getClasspath() { return classpath; }
218     
219     /**
220      * Shared group log accessor method.
221      *
222      * @return the shared group log associated with this service descriptor.
223      */
224     final public String getLog() { return log; }
225     
226     /**
227      * Command accessor method.
228      *
229      * @return the path-qualified java command name associated with this 
230      *     service descriptor.
231      */
232     final public String getServerCommand() { return serverCommand; }
233     
234     /**
235      * Command options accessor method.
236      *
237      * @return the command options associated with this service descriptor.
238      */
239     final public String[] getServerOptions() { 
240 	return (String[])serverOptions.clone(); 
241     }
242     
243     /**
244      * Properties accessor method.
245      *
246      * @return the VM properties associated with this service descriptor.
247      */
248     final public Properties getServerProperties() { 
249 	return (Properties)serverProperties.clone(); 
250     }
251     
252     /**
253      * Activation system host accessor method.
254      *
255      * @return the activation system host associated with this service descriptor.
256      */
257     final public String getActivationSystemHost() { return host; }
258     
259     /**
260      * Activation system port accessor method.
261      *
262      * @return the activation system port associated with this service descriptor.
263      */
264     final public int getActivationSystemPort() { return port; } 
265     
266     private static String[] customizeSharedGroupOptions(
267         String classpath, String[] userOptions)
268     {
269         String[] customOpts = new String[] {"-cp", classpath};
270 	//Prepend classpath so user options can override later on
271 	if (userOptions != null) {
272             String[] tmp = new String[customOpts.length + userOptions.length];
273             System.arraycopy(customOpts, 0, tmp, 0, customOpts.length);
274             System.arraycopy(userOptions, 0, tmp, customOpts.length,
275                              userOptions.length);
276             customOpts = tmp;
277         }
278         return customOpts;
279     }
280     
281     private static Properties convertToProperties(String[] propertyValues) 
282     {
283         Properties properties = new Properties();
284     
285 	if (propertyValues == null || propertyValues.length == 0)
286 	    return properties;
287 
288         if (propertyValues.length % 2 != 0) {
289             throw new IllegalArgumentException(
290                 "The service properties entry has an odd number of elements");
291         }
292         for (int i = 0; i < propertyValues.length; i += 2) {
293             properties.setProperty(propertyValues[i], propertyValues[i + 1]);
294         }
295 	return properties;
296     }
297 
298 
299     private static Properties customizeSharedGroupProperties(
300         String policy, Properties userProperties)
301     {
302         // Avoid passing null properties
303         if (userProperties == null) {
304             userProperties = new Properties();
305         }
306         userProperties.put("java.security.policy", policy);
307 
308 	return userProperties;
309     }
310     
311     /**
312      * Method that attempts to create a shared activation system group from the 
313      * description information provided via constructor parameters.
314      * <P>
315      * This method:
316      * <UL>
317      * <LI> creates a 
318      *      {@link java.rmi.activation.ActivationGroupDesc} with
319      *      the provided constructor parameter information
320      * <LI> calls 
321      *      {@link java.rmi.activation.ActivationSystem#registerGroup(java.rmi.activation.ActivationGroupDesc)
322      *      ActivationSystem.registerGroup()} with the constructed 
323      *      <code>ActivationGroupDesc</code>
324      * <LI> persists the returned
325      *      {@link java.rmi.activation.ActivationGroupID activation group identifier}
326      *      to the shared group log.
327      * <LI> calls 
328      *      {@link java.rmi.activation.ActivationSystem#unregisterGroup(java.rmi.activation.ActivationGroupID) 
329      *      ActivationSystem.unregisterGroup()}
330      *      if an exception occurs while persisting the 
331      *      <code>ActivationGroupID</code>.
332      * </UL>
333      * <EM>Notes:</EM>
334      * <OL>
335      * <LI>Prepends invoking VM's classpath to the server command options. 
336      *     This allows
337      *     subsequent classpath settings to override.
338      * <LI>Adds a <code>"java.security.policy"</code> property with the provided
339      *     policy setting to server properties.
340      * </OL>
341      * @return the 
342      *      {@link java.rmi.activation.ActivationGroupID} for the newly 
343      *      created activation system group instance.
344      *
345      */
346     public Object create(Configuration config) throws Exception {
347         ServiceStarter.ensureSecurityManager();
348         logger.entering(SharedActivationGroupDescriptor.class.getName(),
349 	    "create", new Object[] {config});
350 
351 	if (config == null) {
352 	   throw new NullPointerException(
353 	       "Configuration argument cannot be null");
354 	}
355 
356 //TODO - expand and/or canonicalize classpath components
357 
358 //TODO - check for shared log existence prior to group creation
359     
360 	ActivationSystem sys = 
361 	    ServiceStarter.getActivationSystem(
362 	        getActivationSystemHost(),
363 		getActivationSystemPort(),
364 		config);
365 		
366 	CommandEnvironment cmdToExecute
367 	    = new CommandEnvironment(getServerCommand(), 
368 	                             getServerOptions());
369 	ActivationGroupID gid = null;
370         try {
371 	    gid = sys.registerGroup(
372                 new ActivationGroupDesc(getServerProperties(), 
373 		                        cmdToExecute));
374  	    storeGroupID(getLog(), gid);
375 	} catch (Exception e) {
376             try {
377                 if (gid != null) sys.unregisterGroup(gid);
378             } catch (Exception ee) {
379                 // ignore - did the best we could
380             }
381             if (e instanceof IOException) 
382 	        throw (IOException)e;
383 	    else if (e instanceof ActivationException)
384 	        throw (ActivationException)e;
385 	    else if (e instanceof ClassNotFoundException)
386 	        throw (ClassNotFoundException)e;
387 	    else 
388 	        throw new RuntimeException("Unexpected Exception", e);
389         }
390 
391         logger.exiting(SharedActivationGroupDescriptor.class.getName(), 
392 	    "create", gid);
393 	return gid;
394     }
395 
396     /**
397      * Stores the <code>created</code> object to a well known file
398      * under the provided <code>dir</code> path.
399      */
400     private static void storeGroupID(final String dir, 
401         final ActivationGroupID obj)
402         throws IOException
403     {
404 //TODO - create log dir as a separate step
405         File log = new File(dir);
406         String absDir = log.getAbsolutePath();
407         if (log.exists()) {
408             throw new IOException("Log " + absDir + " exists."
409                 + " Please delete or select another path");
410         }
411         if (!log.mkdir()) {
412             throw new IOException("Could not create directory: " + absDir);
413 // TODO - implement a lock out strategy
414         }
415 
416         File cookieFile = new File(log, GROUP_COOKIE_FILE);
417         ObjectOutputStream oos = null;
418         try {
419             oos = new ObjectOutputStream(
420                 new BufferedOutputStream(
421                     new FileOutputStream(cookieFile)));
422             oos.writeObject(new MarshalledInstance(obj).convertToMarshalledObject());
423             oos.flush();
424 //TODO - file sync?
425 	} catch (IOException e) {
426             cookieFile.delete();
427             throw (IOException)e.fillInStackTrace();
428         } finally {
429             if (oos != null) oos.close();
430         }
431     }
432     
433     /**
434      * Utility method that restores the object stored in a well known file
435      * under the provided <code>dir</code> path.
436      */
437     static ActivationGroupID restoreGroupID(final String dir)
438         throws IOException, ClassNotFoundException
439     {
440         File log = new File(dir);
441         String absDir = log.getAbsolutePath();
442         if (!log.exists() || !log.isDirectory()) {
443             throw new IOException("Log directory [" 
444 	    + absDir + "] does not exist.");
445         }
446 
447         File cookieFile = new File(log, GROUP_COOKIE_FILE);
448         ObjectInputStream ois = null;
449         ActivationGroupID obj = null;
450         try {
451 //TODO - lock out strategy for concurrent r/w file access
452             ois = new ObjectInputStream(
453                       new BufferedInputStream(
454                          new FileInputStream(cookieFile)));
455             MarshalledObject mo = (MarshalledObject)ois.readObject();
456 	    obj = (ActivationGroupID) new MarshalledInstance(mo).get(false);
457         } finally {
458             if (ois != null) ois.close();
459         }
460         return obj;
461     }
462 
463     public String toString() {
464         ArrayList fields = new ArrayList(8);
465         fields.add(policy);
466         fields.add(classpath);
467         fields.add(log);
468         fields.add(serverCommand);
469         fields.add(Arrays.asList(serverOptions));
470         fields.add(serverProperties);
471         fields.add(host);
472         fields.add(Integer.valueOf(port));
473         return fields.toString();
474     }
475     
476     /**
477      * Reads the default serializable field values for this object.  
478      * Also, verifies that the deserialized values are legal.
479      */
480     private void readObject(ObjectInputStream in) 
481         throws IOException, ClassNotFoundException 
482     {
483         in.defaultReadObject();
484 	// Verify that serialized fields
485 	if (policy == null) {
486 	    throw new InvalidObjectException("null policy");
487 	}
488 	if (classpath == null) {
489 	    throw new InvalidObjectException("null class path");
490 	}
491 	if (log == null) {
492 	    throw new InvalidObjectException("null log");
493 	}
494 	if (serverOptions == null) {
495 	    throw new InvalidObjectException("null server options");
496 	}
497 	if (serverProperties == null) {
498 	    throw new InvalidObjectException("null server properties");
499 	}
500 	if (host == null) {
501 	    throw new InvalidObjectException("null activation host name");
502 	}
503 	if (port <= 0) {
504 	    throw new InvalidObjectException("invalid activation port: " + port);
505 	}    
506     }
507     
508     /**
509      * Throws InvalidObjectException, since data for this class is required.
510      */
511     private void readObjectNoData() throws ObjectStreamException {
512 	throw new InvalidObjectException("no data");
513     }
514 
515 }
516 
517