001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    
021    package org.apache.directory.studio.apacheds.jobs;
022    
023    
024    import java.io.File;
025    import java.io.FileOutputStream;
026    import java.io.IOException;
027    import java.util.ArrayList;
028    import java.util.List;
029    
030    import org.apache.directory.studio.apacheds.ApacheDsPluginUtils;
031    import org.apache.directory.studio.apacheds.ConsolesHandler;
032    import org.apache.directory.studio.apacheds.LogMessageConsole;
033    import org.apache.directory.studio.apacheds.configuration.model.ServerConfiguration;
034    import org.apache.directory.studio.apacheds.configuration.model.v153.ServerConfigurationV153;
035    import org.apache.directory.studio.apacheds.configuration.model.v154.ServerConfigurationV154;
036    import org.apache.directory.studio.apacheds.model.Server;
037    import org.apache.directory.studio.apacheds.model.ServerStateEnum;
038    import org.apache.log4j.net.SocketServer;
039    import org.apache.mina.util.AvailablePortFinder;
040    import org.eclipse.core.runtime.CoreException;
041    import org.eclipse.core.runtime.IPath;
042    import org.eclipse.core.runtime.IProgressMonitor;
043    import org.eclipse.core.runtime.IStatus;
044    import org.eclipse.core.runtime.NullProgressMonitor;
045    import org.eclipse.core.runtime.Status;
046    import org.eclipse.core.runtime.jobs.Job;
047    import org.eclipse.debug.core.DebugEvent;
048    import org.eclipse.debug.core.DebugPlugin;
049    import org.eclipse.debug.core.IDebugEventSetListener;
050    import org.eclipse.debug.core.ILaunch;
051    import org.eclipse.debug.core.ILaunchConfiguration;
052    import org.eclipse.debug.core.ILaunchConfigurationType;
053    import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
054    import org.eclipse.debug.core.ILaunchManager;
055    import org.eclipse.debug.core.model.RuntimeProcess;
056    import org.eclipse.debug.ui.IDebugUIConstants;
057    import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
058    import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
059    import org.eclipse.jdt.launching.IVMInstall;
060    import org.eclipse.jdt.launching.JavaRuntime;
061    import org.eclipse.osgi.util.NLS;
062    import org.eclipse.swt.widgets.Display;
063    
064    
065    /**
066     * This class implements a {@link Job} that is used to launch a server.
067     * 
068     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
069     * @version $Rev$, $Date$
070     */
071    public class LaunchServerJob extends Job
072    {
073        /** The server */
074        private Server server;
075    
076        /** The configuration */
077        private ServerConfiguration configuration;
078    
079        /** The launch that will be created when running the server */
080        private ILaunch launch;
081    
082        /** The minimum port number for the socket server */
083        private static final int MIN_PORT = 1024;
084    
085        /** The logs level */
086        private String logsLevel = "WARN"; //$NON-NLS-1$
087    
088        /** The logs pattern */
089        private String logsPattern = "[%d{HH:mm:ss}] %p [%c] - %m%n"; //$NON-NLS-1$
090    
091    
092        /**
093         * Creates a new instance of LaunchServerJob.
094         * 
095         * @param server
096         *            the server
097         * @param configuration
098         *            the configuration
099         */
100        public LaunchServerJob( Server server, ServerConfiguration configuration )
101        {
102            super( "" ); //$NON-NLS-1$
103            this.server = server;
104            this.configuration = configuration;
105        }
106    
107    
108        /*
109         * (non-Javadoc)
110         * 
111         * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
112         */
113        protected IStatus run( IProgressMonitor monitor )
114        {
115            // Setting the name of the Job
116            setName( NLS.bind( Messages.getString( "LaunchServerJob.Starting" ), new String[] { server.getName() } ) ); //$NON-NLS-1$
117    
118            // Setting the server in a "starting" state
119            server.setState( ServerStateEnum.STARTING );
120            writeToInfoConsoleMessageStream( Messages.getString( "LaunchServerJob.ServerStarting" ) ); //$NON-NLS-1$
121    
122            // Getting the first available port for the Log4J socket server
123            int port = AvailablePortFinder.getNextAvailable( MIN_PORT );
124    
125            // Launching the socket server
126            launchSocketServer( port );
127    
128            // Overwriting the server's log4j.properties file
129            try
130            {
131                overwriteServersLog4jPropertiesFile( port );
132            }
133            catch ( IOException e )
134            {
135                ApacheDsPluginUtils.reportError( Messages.getString( "LaunchServerJob.ErrorOverwritingLog" ) //$NON-NLS-1$
136                    + e.getMessage() );
137            }
138    
139            // Launching Apache DS
140            launchApacheDS();
141    
142            // Starting the startup listener thread
143            startStartupListenerThread();
144    
145            // Starting the "terminate" listener thread
146            startTerminateListenerThread();
147    
148            return Status.OK_STATUS;
149        }
150    
151    
152        /**
153         * Starts the startup listener thread.
154         */
155        private void startStartupListenerThread()
156        {
157            // Getting the current time
158            long startTime = System.currentTimeMillis();
159    
160            // Calculating the watch dog time
161            final long watchDog = startTime + ( 1000 * 60 * 3 ); // 3 minutes
162    
163            // Creating the thread
164            Thread thread = new Thread()
165            {
166                public void run()
167                {
168                    // Looping until the end of the watchdog
169                    while ( ( System.currentTimeMillis() < watchDog ) && ( ServerStateEnum.STARTING == server.getState() ) )
170                    {
171                        try
172                        {
173                            // Getting the port to test
174                            int port = getTestingPort( configuration );
175    
176                            // If no protocol is enabled, we pass this and 
177                            // declare the server as started
178                            if ( port != 0 )
179                            {
180                                // Trying to see if the port is available
181                                if ( AvailablePortFinder.available( port ) )
182                                {
183                                    // The port is still available
184                                    throw new Exception();
185                                }
186                            }
187    
188                            // If we pass the creation of the context, it means
189                            // the server is correctly started
190    
191                            // We set the state of the server to 'started'...
192                            server.setState( ServerStateEnum.STARTED );
193                            writeToInfoConsoleMessageStream( Messages.getString( "LaunchServerJob.ServerStarted" ) ); //$NON-NLS-1$
194    
195                            // ... and we exit the thread
196                            return;
197                        }
198                        catch ( Exception e )
199                        {
200                            // If we get an exception,it means the server is not 
201                            // yet started
202    
203                            // We just wait one second before starting the test once
204                            // again
205                            try
206                            {
207                                Thread.sleep( 1000 );
208                            }
209                            catch ( InterruptedException e1 )
210                            {
211                                // Nothing to do...
212                            }
213                        }
214                    }
215    
216                    // If at the end of the watch dog the state of the server is
217                    // still 'starting' then, we declare the server as 'stopped'
218                    if ( ServerStateEnum.STARTING == server.getState() )
219                    {
220                        server.setState( ServerStateEnum.STOPPED );
221                        writeToInfoConsoleMessageStream( Messages.getString( "LaunchServerJob.ServerStopped" ) ); //$NON-NLS-1$
222                    }
223                }
224    
225    
226                /**
227                 * Gets the testing port.
228                 *
229                 * @param configuration
230                 *      the server configuration
231                 * @return
232                 *      the testing port
233                 */
234                private int getTestingPort( ServerConfiguration configuration )
235                {
236                    if ( configuration instanceof ServerConfigurationV154 )
237                    {
238                        return getTestingPortVersion154( ( ServerConfigurationV154 ) configuration );
239                    }
240                    else if ( configuration instanceof ServerConfigurationV153 )
241                    {
242                        return getTestingPortVersion153( ( ServerConfigurationV153 ) configuration );
243                    }
244                    else
245                    {
246                        return 0;
247                    }
248                }
249    
250    
251                /**
252                 * Gets the testing port.
253                 *
254                 * @param configuration
255                 *      the 1.5.3 server configuration
256                 * @return
257                 *      the testing port
258                 */
259                private int getTestingPortVersion153( ServerConfigurationV153 configuration )
260                {
261                    // LDAP
262                    if ( configuration.isEnableLdap() )
263                    {
264                        return configuration.getLdapPort();
265                    }
266                    // LDAPS
267                    else if ( configuration.isEnableLdaps() )
268                    {
269                        return configuration.getLdapsPort();
270                    }
271                    // Kerberos
272                    else if ( configuration.isEnableKerberos() )
273                    {
274                        return configuration.getKerberosPort();
275                    }
276                    // DNS
277                    else if ( configuration.isEnableDns() )
278                    {
279                        return configuration.getDnsPort();
280                    }
281                    // NTP
282                    else if ( configuration.isEnableNtp() )
283                    {
284                        return configuration.getNtpPort();
285                    }
286                    // ChangePassword
287                    else if ( configuration.isEnableChangePassword() )
288                    {
289                        return configuration.getChangePasswordPort();
290                    }
291                    else
292                    {
293                        return 0;
294                    }
295                }
296    
297    
298                /**
299                 * Gets the testing port.
300                 *
301                 * @param configuration
302                 *      the 1.5.4 server configuration
303                 * @return
304                 *      the testing port
305                 */
306                private int getTestingPortVersion154( ServerConfigurationV154 configuration )
307                {
308                    // LDAP
309                    if ( configuration.isEnableLdap() )
310                    {
311                        return configuration.getLdapPort();
312                    }
313                    // LDAPS
314                    else if ( configuration.isEnableLdaps() )
315                    {
316                        return configuration.getLdapsPort();
317                    }
318                    // Kerberos
319                    else if ( configuration.isEnableKerberos() )
320                    {
321                        return configuration.getKerberosPort();
322                    }
323                    // DNS
324                    else if ( configuration.isEnableDns() )
325                    {
326                        return configuration.getDnsPort();
327                    }
328                    // NTP
329                    else if ( configuration.isEnableNtp() )
330                    {
331                        return configuration.getNtpPort();
332                    }
333                    // ChangePassword
334                    else if ( configuration.isEnableChangePassword() )
335                    {
336                        return configuration.getChangePasswordPort();
337                    }
338                    else
339                    {
340                        return 0;
341                    }
342                }
343            };
344    
345            // Starting the thread
346            thread.start();
347        }
348    
349    
350        /**
351         * Writes the given message to the Info console message stream.
352         * 
353         * @param message
354         *            the message
355         */
356        private void writeToInfoConsoleMessageStream( final String message )
357        {
358            Display.getDefault().asyncExec( new Runnable()
359            {
360                public void run()
361                {
362                    LogMessageConsole console = ConsolesHandler.getDefault().getLogMessageConsole( server.getId() );
363                    try
364                    {
365                        console.getInfoConsoleMessageStream().write( message );
366                    }
367                    catch ( IOException e )
368                    {
369                        ApacheDsPluginUtils.reportError( Messages.getString( "LaunchServerJob.ErrorWritingConsole" ) //$NON-NLS-1$
370                            + e.getMessage() );
371                    }
372                }
373            } );
374        }
375    
376    
377        /**
378         * Starting the "terminate" listener thread.
379         */
380        private void startTerminateListenerThread()
381        {
382            // Creating the thread
383            Thread thread = new Thread()
384            {
385                /** The debug event listener */
386                private IDebugEventSetListener debugEventSetListener;
387    
388    
389                public void run()
390                {
391                    // Creating the listener
392                    debugEventSetListener = new IDebugEventSetListener()
393                    {
394                        public void handleDebugEvents( DebugEvent[] events )
395                        {
396                            // Looping on the debug events array
397                            for ( DebugEvent debugEvent : events )
398                            {
399                                // We only care of event with kind equals to
400                                // 'terminate'
401                                if ( debugEvent.getKind() == DebugEvent.TERMINATE )
402                                {
403                                    // Getting the source of the debug event
404                                    Object source = debugEvent.getSource();
405                                    if ( source instanceof RuntimeProcess )
406                                    {
407                                        RuntimeProcess runtimeProcess = ( RuntimeProcess ) source;
408    
409                                        // Getting the associated launch
410                                        ILaunch debugEventLaunch = runtimeProcess.getLaunch();
411                                        if ( debugEventLaunch.equals( launch ) )
412                                        {
413                                            // The launch we had created is now terminated
414                                            // The server is now stopped
415                                            server.setState( ServerStateEnum.STOPPED );
416    
417                                            // Removing the listener
418                                            DebugPlugin.getDefault().removeDebugEventListener( debugEventSetListener );
419                                        }
420                                    }
421                                }
422                            }
423                        }
424                    };
425    
426                    // Adding the listener
427                    DebugPlugin.getDefault().addDebugEventListener( debugEventSetListener );
428                }
429            };
430    
431            // Starting the thread
432            thread.start();
433        }
434    
435    
436        /**
437         * Launches a Log4J {@link SocketServer} which will be used to redirect the
438         * logs of Apache DS to the console.
439         * 
440         * @param port
441         *            the port
442         * @param
443         * 
444         */
445        private void launchSocketServer( int port )
446        {
447            final int finalPort = port;
448            final IPath serverSocketFolderPath = ApacheDsPluginUtils.getApacheDsServersFolder().append( server.getId() )
449                .append( "serverSocket" ); //$NON-NLS-1$
450            final IPath log4jPropertiesFilePath = serverSocketFolderPath.append( "log4j.properties" ); //$NON-NLS-1$
451    
452            // Creating a new thread for the SocketServer
453            Thread thread = new Thread()
454            {
455                public void run()
456                {
457                    SocketServer.main( new String[]
458                        { "" + finalPort, log4jPropertiesFilePath.toOSString(), serverSocketFolderPath.toOSString() } ); //$NON-NLS-1$
459                }
460            };
461    
462            // Launching the SocketServer
463            thread.start();
464        }
465    
466    
467        /**
468         * Overwrites the log4j.properties file of the server with the given port
469         * number.
470         * 
471         * @param port
472         *            the port
473         * @throws IOException
474         */
475        private void overwriteServersLog4jPropertiesFile( int port ) throws IOException
476        {
477            IPath confFolderPath = ApacheDsPluginUtils.getApacheDsServersFolder().append( server.getId() ).append( "conf" ); //$NON-NLS-1$
478            File confFolder = new File( confFolderPath.toOSString() );
479            ApacheDsPluginUtils.createServersLog4jPropertiesFile( new FileOutputStream( new File( confFolder,
480                "log4j.properties" ) ), port, logsLevel, logsPattern ); //$NON-NLS-1$
481        }
482    
483    
484        /**
485         * Launches Apache DS using a launch configuration.
486         */
487        private void launchApacheDS()
488        {
489            try
490            {
491                // Getting the default VM installation
492                IVMInstall vmInstall = JavaRuntime.getDefaultVMInstall();
493    
494                // Creating a new editable launch configuration
495                ILaunchConfigurationType type = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurationType(
496                    IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION );
497                ILaunchConfigurationWorkingCopy workingCopy = type.newInstance( null, NLS.bind( Messages
498                    .getString( "LaunchServerJob.StartingServer" ), new String[] { server.getName() } ) ); //$NON-NLS-1$
499    
500                // Setting the JRE container path attribute
501                workingCopy.setAttribute( IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH, vmInstall
502                    .getInstallLocation().toString() );
503    
504                // Setting the main type attribute
505                workingCopy.setAttribute( IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
506                    "org.apache.directory.studio.apacheds.Launcher" ); //$NON-NLS-1$
507    
508                // Creating the classpath list
509                List<String> classpath = new ArrayList<String>();
510                IPath apacheDsLibrariesFolder = ApacheDsPluginUtils.getApacheDsLibrariesFolder( server );
511                for ( String library : ApacheDsPluginUtils.getApacheDsLibraries( server ) )
512                {
513                    IRuntimeClasspathEntry libraryClasspathEntry = JavaRuntime
514                        .newArchiveRuntimeClasspathEntry( apacheDsLibrariesFolder.append( library ) );
515                    libraryClasspathEntry.setClasspathProperty( IRuntimeClasspathEntry.USER_CLASSES );
516    
517                    classpath.add( libraryClasspathEntry.getMemento() );
518                }
519    
520                // Setting the classpath type attribute
521                workingCopy.setAttribute( IJavaLaunchConfigurationConstants.ATTR_CLASSPATH, classpath );
522    
523                // Setting the default classpath type attribute to false
524                workingCopy.setAttribute( IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH, false );
525    
526                // The server folder path
527                IPath serverFolderPath = ApacheDsPluginUtils.getApacheDsServersFolder().append( server.getId() );
528    
529                // Setting the program arguments attribute
530                workingCopy.setAttribute( IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, "\"" //$NON-NLS-1$
531                    + serverFolderPath.toOSString() + "\"" ); //$NON-NLS-1$
532    
533                // Creating the VM arguments string
534                StringBuffer vmArguments = new StringBuffer();
535                vmArguments.append( "-Dlog4j.configuration=file:\"" //$NON-NLS-1$
536                    + serverFolderPath.append( "conf" ).append( "log4j.properties" ).toOSString() + "\"" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
537                vmArguments.append( " " ); //$NON-NLS-1$
538                vmArguments.append( "-Dapacheds.var.dir=\"" + serverFolderPath.toOSString() + "\"" ); //$NON-NLS-1$ //$NON-NLS-2$
539                vmArguments.append( " " ); //$NON-NLS-1$
540                vmArguments.append( "-Dapacheds.log.dir=\"" + serverFolderPath.append( "log" ).toOSString() + "\"" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
541                vmArguments.append( " " ); //$NON-NLS-1$
542                vmArguments.append( "-Dapacheds.run.dir=\"" + serverFolderPath.append( "run" ).toOSString() + "\"" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
543                vmArguments.append( " " ); //$NON-NLS-1$
544                vmArguments.append( "-Dapacheds.instance=\"" + server.getName() + "\"" ); //$NON-NLS-1$ //$NON-NLS-2$
545    
546                // Setting the VM arguments attribute
547                workingCopy.setAttribute( IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, vmArguments.toString() );
548    
549                // Setting the launch configuration as private
550                workingCopy.setAttribute( IDebugUIConstants.ATTR_PRIVATE, true );
551    
552                // Indicating that we don't want any console to show up
553                workingCopy.setAttribute( DebugPlugin.ATTR_CAPTURE_OUTPUT, false );
554    
555                // Saving the launch configuration
556                ILaunchConfiguration configuration = workingCopy.doSave();
557    
558                // Launching the launch configuration
559                launch = configuration.launch( ILaunchManager.RUN_MODE, new NullProgressMonitor() );
560            }
561            catch ( CoreException e )
562            {
563                ApacheDsPluginUtils.reportError( Messages.getString( "LaunchServerJob.ErrorLaunching" ) + e.getMessage() ); //$NON-NLS-1$
564            }
565        }
566    
567    
568        /**
569         * Gets the associated launch.
570         * 
571         * @return the associated launch
572         */
573        public ILaunch getLaunch()
574        {
575            return launch;
576        }
577    
578    
579        /**
580         * Sets the logs level.
581         * 
582         * @param logsLevel
583         *            the logs level
584         */
585        public void setLogsLevel( String logsLevel )
586        {
587            this.logsLevel = logsLevel;
588        }
589    
590    
591        /**
592         * Sets the logs pattern.
593         * 
594         * @param logsPattern
595         *            the logs pattern
596         */
597        public void setLogsPattern( String logsPattern )
598        {
599            this.logsPattern = logsPattern;
600        }
601    }