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) throws IOException{
192 	this(
193 		Valid.notNull(arg.get("policy", null, String.class), "Policy cannot be null"),
194 		Valid.notNull(arg.get("classpath", null, String.class), "Classpath cannot be null"),
195 		Valid.notNull(arg.get("log",null, String.class), "log cannot be null"),
196 		arg.get("serverCommand", null, String.class),
197 		arg.get("serverOptions", null, String[].class),
198 		arg.get("serverProperties", null, String[].class),
199 		arg.get("host", null, String.class),
200 		arg.get("port", 0)
201 	);
202     }
203  
204     /**
205      * Policy accessor method.
206      *
207      * @return the policy location associated with this service descriptor.
208      */
209     final public String getPolicy() { return policy; }
210     
211     /**
212      * Classpath accessor method.
213      *
214      * @return classpath associated with this service descriptor.
215      */
216     final public String getClasspath() { return classpath; }
217     
218     /**
219      * Shared group log accessor method.
220      *
221      * @return the shared group log associated with this service descriptor.
222      */
223     final public String getLog() { return log; }
224     
225     /**
226      * Command accessor method.
227      *
228      * @return the path-qualified java command name associated with this 
229      *     service descriptor.
230      */
231     final public String getServerCommand() { return serverCommand; }
232     
233     /**
234      * Command options accessor method.
235      *
236      * @return the command options associated with this service descriptor.
237      */
238     final public String[] getServerOptions() { 
239 	return (String[])serverOptions.clone(); 
240     }
241     
242     /**
243      * Properties accessor method.
244      *
245      * @return the VM properties associated with this service descriptor.
246      */
247     final public Properties getServerProperties() { 
248 	return (Properties)serverProperties.clone(); 
249     }
250     
251     /**
252      * Activation system host accessor method.
253      *
254      * @return the activation system host associated with this service descriptor.
255      */
256     final public String getActivationSystemHost() { return host; }
257     
258     /**
259      * Activation system port accessor method.
260      *
261      * @return the activation system port associated with this service descriptor.
262      */
263     final public int getActivationSystemPort() { return port; } 
264     
265     private static String[] customizeSharedGroupOptions(
266         String classpath, String[] userOptions)
267     {
268         String[] customOpts = new String[] {"-cp", classpath};
269 	//Prepend classpath so user options can override later on
270 	if (userOptions != null) {
271             String[] tmp = new String[customOpts.length + userOptions.length];
272             System.arraycopy(customOpts, 0, tmp, 0, customOpts.length);
273             System.arraycopy(userOptions, 0, tmp, customOpts.length,
274                              userOptions.length);
275             customOpts = tmp;
276         }
277         return customOpts;
278     }
279     
280     private static Properties convertToProperties(String[] propertyValues) 
281     {
282         Properties properties = new Properties();
283     
284 	if (propertyValues == null || propertyValues.length == 0)
285 	    return properties;
286 
287         if (propertyValues.length % 2 != 0) {
288             throw new IllegalArgumentException(
289                 "The service properties entry has an odd number of elements");
290         }
291         for (int i = 0; i < propertyValues.length; i += 2) {
292             properties.setProperty(propertyValues[i], propertyValues[i + 1]);
293         }
294 	return properties;
295     }
296 
297 
298     private static Properties customizeSharedGroupProperties(
299         String policy, Properties userProperties)
300     {
301         // Avoid passing null properties
302         if (userProperties == null) {
303             userProperties = new Properties();
304         }
305         userProperties.put("java.security.policy", policy);
306 
307 	return userProperties;
308     }
309     
310     /**
311      * Method that attempts to create a shared activation system group from the 
312      * description information provided via constructor parameters.
313      * <P>
314      * This method:
315      * <UL>
316      * <LI> creates a 
317      *      {@link java.rmi.activation.ActivationGroupDesc} with
318      *      the provided constructor parameter information
319      * <LI> calls 
320      *      {@link java.rmi.activation.ActivationSystem#registerGroup(java.rmi.activation.ActivationGroupDesc)
321      *      ActivationSystem.registerGroup()} with the constructed 
322      *      <code>ActivationGroupDesc</code>
323      * <LI> persists the returned
324      *      {@link java.rmi.activation.ActivationGroupID activation group identifier}
325      *      to the shared group log.
326      * <LI> calls 
327      *      {@link java.rmi.activation.ActivationSystem#unregisterGroup(java.rmi.activation.ActivationGroupID) 
328      *      ActivationSystem.unregisterGroup()}
329      *      if an exception occurs while persisting the 
330      *      <code>ActivationGroupID</code>.
331      * </UL>
332      * <EM>Notes:</EM>
333      * <OL>
334      * <LI>Prepends invoking VM's classpath to the server command options. 
335      *     This allows
336      *     subsequent classpath settings to override.
337      * <LI>Adds a <code>"java.security.policy"</code> property with the provided
338      *     policy setting to server properties.
339      * </OL>
340      * @return the 
341      *      {@link java.rmi.activation.ActivationGroupID} for the newly 
342      *      created activation system group instance.
343      *
344      */
345     public Object create(Configuration config) throws Exception {
346         ServiceStarter.ensureSecurityManager();
347         logger.entering(SharedActivationGroupDescriptor.class.getName(),
348 	    "create", new Object[] {config});
349 
350 	if (config == null) {
351 	   throw new NullPointerException(
352 	       "Configuration argument cannot be null");
353 	}
354 
355 //TODO - expand and/or canonicalize classpath components
356 
357 //TODO - check for shared log existence prior to group creation
358     
359 	ActivationSystem sys = 
360 	    ServiceStarter.getActivationSystem(
361 	        getActivationSystemHost(),
362 		getActivationSystemPort(),
363 		config);
364 		
365 	CommandEnvironment cmdToExecute
366 	    = new CommandEnvironment(getServerCommand(), 
367 	                             getServerOptions());
368 	ActivationGroupID gid = null;
369         try {
370 	    gid = sys.registerGroup(
371                 new ActivationGroupDesc(getServerProperties(), 
372 		                        cmdToExecute));
373  	    storeGroupID(getLog(), gid);
374 	} catch (Exception e) {
375             try {
376                 if (gid != null) sys.unregisterGroup(gid);
377             } catch (Exception ee) {
378                 // ignore - did the best we could
379             }
380             if (e instanceof IOException) 
381 	        throw (IOException)e;
382 	    else if (e instanceof ActivationException)
383 	        throw (ActivationException)e;
384 	    else if (e instanceof ClassNotFoundException)
385 	        throw (ClassNotFoundException)e;
386 	    else 
387 	        throw new RuntimeException("Unexpected Exception", e);
388         }
389 
390         logger.exiting(SharedActivationGroupDescriptor.class.getName(), 
391 	    "create", gid);
392 	return gid;
393     }
394 
395     /**
396      * Stores the <code>created</code> object to a well known file
397      * under the provided <code>dir</code> path.
398      */
399     private static void storeGroupID(final String dir, 
400         final ActivationGroupID obj)
401         throws IOException
402     {
403 //TODO - create log dir as a separate step
404         File log = new File(dir);
405         String absDir = log.getAbsolutePath();
406         if (log.exists()) {
407             throw new IOException("Log " + absDir + " exists."
408                 + " Please delete or select another path");
409         }
410         if (!log.mkdir()) {
411             throw new IOException("Could not create directory: " + absDir);
412 // TODO - implement a lock out strategy
413         }
414 
415         File cookieFile = new File(log, GROUP_COOKIE_FILE);
416         ObjectOutputStream oos = null;
417         try {
418             oos = new ObjectOutputStream(
419                 new BufferedOutputStream(
420                     new FileOutputStream(cookieFile)));
421             oos.writeObject(new MarshalledInstance(obj).convertToMarshalledObject());
422             oos.flush();
423 //TODO - file sync?
424 	} catch (IOException e) {
425             cookieFile.delete();
426             throw (IOException)e.fillInStackTrace();
427         } finally {
428             if (oos != null) oos.close();
429         }
430     }
431     
432     /**
433      * Utility method that restores the object stored in a well known file
434      * under the provided <code>dir</code> path.
435      */
436     static ActivationGroupID restoreGroupID(final String dir)
437         throws IOException, ClassNotFoundException
438     {
439         File log = new File(dir);
440         String absDir = log.getAbsolutePath();
441         if (!log.exists() || !log.isDirectory()) {
442             throw new IOException("Log directory [" 
443 	    + absDir + "] does not exist.");
444         }
445 
446         File cookieFile = new File(log, GROUP_COOKIE_FILE);
447         ObjectInputStream ois = null;
448         ActivationGroupID obj = null;
449         try {
450 //TODO - lock out strategy for concurrent r/w file access
451             ois = new ObjectInputStream(
452                       new BufferedInputStream(
453                          new FileInputStream(cookieFile)));
454             MarshalledObject mo = (MarshalledObject)ois.readObject();
455 	    obj = (ActivationGroupID) new MarshalledInstance(mo).get(false);
456         } finally {
457             if (ois != null) ois.close();
458         }
459         return obj;
460     }
461 
462     public String toString() {
463         ArrayList fields = new ArrayList(8);
464         fields.add(policy);
465         fields.add(classpath);
466         fields.add(log);
467         fields.add(serverCommand);
468         fields.add(Arrays.asList(serverOptions));
469         fields.add(serverProperties);
470         fields.add(host);
471         fields.add(Integer.valueOf(port));
472         return fields.toString();
473     }
474     
475     /**
476      * Reads the default serializable field values for this object.  
477      * Also, verifies that the deserialized values are legal.
478      */
479     private void readObject(ObjectInputStream in) 
480         throws IOException, ClassNotFoundException 
481     {
482         in.defaultReadObject();
483 	// Verify that serialized fields
484 	if (policy == null) {
485 	    throw new InvalidObjectException("null policy");
486 	}
487 	if (classpath == null) {
488 	    throw new InvalidObjectException("null class path");
489 	}
490 	if (log == null) {
491 	    throw new InvalidObjectException("null log");
492 	}
493 	if (serverOptions == null) {
494 	    throw new InvalidObjectException("null server options");
495 	}
496 	if (serverProperties == null) {
497 	    throw new InvalidObjectException("null server properties");
498 	}
499 	if (host == null) {
500 	    throw new InvalidObjectException("null activation host name");
501 	}
502 	if (port <= 0) {
503 	    throw new InvalidObjectException("invalid activation port: " + port);
504 	}    
505     }
506     
507     /**
508      * Throws InvalidObjectException, since data for this class is required.
509      */
510     private void readObjectNoData() throws ObjectStreamException {
511 	throw new InvalidObjectException("no data");
512     }
513 
514 }
515 
516