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