View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to you under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
9    * or agreed to in writing, software distributed under the License is
10   * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11   * KIND, either express or implied. See the License for the specific language
12   * governing permissions and limitations under the License.
13   */
14  package org.apache.maven.plugin.eclipse.reader;
15  
16  import java.io.DataInputStream;
17  import java.io.File;
18  import java.io.FileInputStream;
19  import java.io.FileNotFoundException;
20  import java.io.FileReader;
21  import java.io.IOException;
22  import java.io.StringReader;
23  import java.net.URI;
24  import java.net.URISyntaxException;
25  import java.text.MessageFormat;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Properties;
32  import java.util.Set;
33  import java.util.jar.JarFile;
34  
35  import org.apache.maven.plugin.eclipse.Messages;
36  import org.apache.maven.plugin.eclipse.WorkspaceConfiguration;
37  import org.apache.maven.plugin.ide.IdeDependency;
38  import org.apache.maven.plugin.ide.IdeUtils;
39  import org.apache.maven.plugin.logging.Log;
40  import org.apache.maven.project.MavenProject;
41  import org.codehaus.plexus.util.IOUtil;
42  import org.codehaus.plexus.util.xml.Xpp3Dom;
43  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
44  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
45  import org.eclipse.core.internal.localstore.SafeChunkyInputStream;
46  
47  /**
48   * Scan the eclipse workspace and create a array with {@link IdeDependency} for all found artefacts.
49   * 
50   * @author Richard van Nieuwenhoven
51   * @version $Id: ReadWorkspaceLocations.java 1681500 2015-05-24 17:25:06Z agudian $
52   */
53  public class ReadWorkspaceLocations
54  {
55  
56      public static final String BINARY_LOCATION_FILE = ".location";
57  
58      public static final String METADATA_PLUGINS_ORG_ECLIPSE_CORE_RESOURCES_PROJECTS =
59          ".metadata/.plugins/org.eclipse.core.resources/.projects";
60  
61      private static final String[] PARENT_VERSION = new String[] { "parent", "version" };
62  
63      private static final String[] PARENT_GROUP_ID = new String[] { "parent", "groupId" };
64  
65      private static final String[] PACKAGING = new String[] { "packaging" };
66  
67      private static final String[] VERSION = new String[] { "version" };
68  
69      private static final String[] GROUP_ID = new String[] { "groupId" };
70  
71      private static final String[] ARTEFACT_ID = new String[] { "artifactId" };
72  
73      private static final String METADATA_PLUGINS_ORG_ECLIPSE_CORE_RUNTIME_LAUNCHING_PREFS =
74          ".metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.launching.prefs";
75  
76      private static final String METADATA_PLUGINS_ORG_ECLIPSE_CORE_RUNTIME_PREFS_VM_KEY =
77          "org.eclipse.jdt.launching.PREF_VM_XML";
78  
79      private static final String METADATA_PLUGINS_ORG_ECLIPSE_CORE_RUNTIME_SERVER_PREFS =
80          ".metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.wst.server.core.prefs";
81  
82      private static final String METADATA_PLUGINS_ORG_ECLIPSE_CORE_RUNTIME_PREFS_RUNTIMES_KEY = "runtimes";
83  
84      private static final String CLASSPATHENTRY_DEFAULT = "org.eclipse.jdt.launching.JRE_CONTAINER";
85  
86      private static final String CLASSPATHENTRY_STANDARD = CLASSPATHENTRY_DEFAULT
87          + "/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/";
88  
89      private static final String CLASSPATHENTRY_FORMAT = ReadWorkspaceLocations.CLASSPATHENTRY_DEFAULT + "/{0}/{1}";
90  
91      public void init( Log log, WorkspaceConfiguration workspaceConfiguration, MavenProject project,
92                        String wtpDefaultServer, boolean preferStandardClasspathContainer )
93      {
94          workspaceConfiguration.setDefaultClasspathContainer( detectDefaultJREContainer( workspaceConfiguration,
95                                                                                          project,
96                                                                                          preferStandardClasspathContainer,
97                                                                                          log ) );
98          readWorkspace( workspaceConfiguration, log );
99          detectWTPDefaultServer( workspaceConfiguration, wtpDefaultServer, log );
100     }
101 
102     /**
103      * Detect WTP Default Server. Do nothing if tehre are no defined servers in the settings.
104      * 
105      * @param workspaceConfiguration the workspace configuration
106      * @param wtpDefaultServer Default server
107      * @param log the log
108      */
109     private void detectWTPDefaultServer( WorkspaceConfiguration workspaceConfiguration, String wtpDefaultServer, Log log )
110     {
111         Map<String, String> servers = readDefinedServers( workspaceConfiguration, log );
112         if ( servers == null || servers.isEmpty() )
113         {
114             return;
115         }
116         if ( wtpDefaultServer != null )
117         {
118             Set<String> ids = servers.keySet();
119             // first we try the exact match
120             Iterator<String> idIterator = ids.iterator();
121             while ( workspaceConfiguration.getDefaultDeployServerId() == null && idIterator.hasNext() )
122             {
123                 String id = idIterator.next();
124                 String name = servers.get( id );
125                 if ( wtpDefaultServer.equals( id ) || wtpDefaultServer.equals( name ) )
126                 {
127                     workspaceConfiguration.setDefaultDeployServerId( id );
128                     workspaceConfiguration.setDefaultDeployServerName( name );
129                 }
130             }
131             if ( workspaceConfiguration.getDefaultDeployServerId() == null )
132             {
133                 log.info( "no exact wtp server match." );
134                 // now we will try the substring match
135                 idIterator = ids.iterator();
136                 while ( workspaceConfiguration.getDefaultDeployServerId() == null && idIterator.hasNext() )
137                 {
138                     String id = idIterator.next();
139                     String name = servers.get( id );
140                     if ( id.contains( wtpDefaultServer ) || name.contains( wtpDefaultServer ) )
141                     {
142                         workspaceConfiguration.setDefaultDeployServerId( id );
143                         workspaceConfiguration.setDefaultDeployServerName( name );
144                     }
145                 }
146             }
147         }
148         if ( workspaceConfiguration.getDefaultDeployServerId() == null && servers.size() > 0 )
149         {
150             // now take the default server
151             log.info( "no substring wtp server match." );
152             workspaceConfiguration.setDefaultDeployServerId( servers.get( "" ) );
153             workspaceConfiguration.setDefaultDeployServerName( servers.get( workspaceConfiguration.getDefaultDeployServerId() ) );
154         }
155         log.info( "Using as WTP server : " + workspaceConfiguration.getDefaultDeployServerName() );
156     }
157 
158     /**
159      * Take the compiler executable and try to find a JRE that contains that compiler.
160      * 
161      * @param rawExecutable the executable with the complete path.
162      * @param jreMap the map with defined JRE's.
163      * @param logger the logger to log the error's
164      * @return the found container or null if non found.
165      */
166     private String getContainerFromExecutable( String rawExecutable, Map<String, String> jreMap, Log logger )
167     {
168         String foundContainer;
169         if ( rawExecutable != null )
170         {
171             String executable;
172             try
173             {
174                 executable = new File( rawExecutable ).getCanonicalPath();
175                 logger.debug( "detected executable: " + executable );
176             }
177             catch ( Exception e )
178             {
179                 return null;
180             }
181             File executableFile = new File( executable );
182             while ( executableFile != null )
183             {
184                 foundContainer = jreMap.get( executableFile.getPath() );
185                 if ( foundContainer != null )
186                 {
187                     logger.debug( "detected classpathContainer from executable: " + foundContainer );
188                     return foundContainer;
189 
190                 }
191                 executableFile = executableFile.getParentFile();
192             }
193         }
194         return null;
195     }
196 
197     /**
198      * Search the default JREContainer from eclipse for the current MavenProject
199      * 
200      * @param workspaceConfiguration the location of the workspace.
201      * @param project the maven project the get the configuration
202      * @param preferStandardClasspathContainer prefer using the standard classpath container name
203      * @param logger the logger for errors
204      */
205     private String detectDefaultJREContainer( WorkspaceConfiguration workspaceConfiguration, MavenProject project,
206                                               boolean preferStandardClasspathContainer, Log logger )
207     {
208         File workspaceDirectory = workspaceConfiguration.getWorkspaceDirectory();
209 
210         Map<String, String> jreMap =
211             readAvailableJREs( preferStandardClasspathContainer ? null : workspaceDirectory, logger );
212         if ( jreMap != null )
213         {
214             String foundContainer = null;
215             if ( preferStandardClasspathContainer )
216             {
217                 foundContainer = getContainerFromSourceVersion( project, jreMap, logger );
218                 if ( foundContainer != null )
219                 {
220                     return foundContainer;
221                 }
222             }
223 
224             foundContainer =
225                 getContainerFromExecutable( System.getProperty( "maven.compiler.executable" ), jreMap, logger );
226             if ( foundContainer != null )
227             {
228                 return foundContainer;
229             }
230 
231             foundContainer =
232                 getContainerFromExecutable( IdeUtils.getCompilerPluginSetting( project, "executable" ), jreMap, logger );
233             if ( foundContainer != null )
234             {
235                 return foundContainer;
236             }
237 
238             if ( !preferStandardClasspathContainer )
239             {
240                 foundContainer = getContainerFromSourceVersion( project, jreMap, logger );
241                 if ( foundContainer != null )
242                 {
243                     return foundContainer;
244                 }
245             }
246 
247             foundContainer = getContainerFromExecutable( System.getProperty( "java.home" ), jreMap, logger );
248             if ( foundContainer != null )
249             {
250                 return foundContainer;
251             }
252         }
253         return ReadWorkspaceLocations.CLASSPATHENTRY_DEFAULT;
254     }
255 
256     private String getContainerFromSourceVersion( MavenProject project, Map<String, String> jreMap, Log logger )
257     {
258         String foundContainer;
259         String sourceVersion = IdeUtils.getCompilerSourceVersion( project );
260         foundContainer = (String) jreMap.get( sourceVersion );
261         if ( foundContainer != null )
262         {
263             logger.debug( "detected classpathContainer from sourceVersion(" + sourceVersion + "): " + foundContainer );
264         }
265         return foundContainer;
266     }
267 
268     /**
269      * Get the project location for a project in the eclipse metadata.
270      * 
271      * @param workspaceLocation the location of the workspace
272      * @param project the project subdirectory in the metadata
273      * @return the full path to the project.
274      * @throws IOException failures to read location file
275      * @throws URISyntaxException failures to read location file
276      */
277     /* package */File getProjectLocation( File workspaceLocation, File project )
278         throws IOException, URISyntaxException
279     {
280         File location = new File( project, ReadWorkspaceLocations.BINARY_LOCATION_FILE );
281         if ( location.exists() )
282         {
283             SafeChunkyInputStream fileInputStream = null;
284             DataInputStream dataInputStream = null;
285             try
286             {
287                 fileInputStream = new SafeChunkyInputStream( location );
288                 dataInputStream = new DataInputStream( fileInputStream );
289                 String file = dataInputStream.readUTF().trim();
290 
291                 if ( file.length() > 0 )
292                 {
293                     if ( !file.startsWith( "URI//" ) )
294                     {
295                         throw new IOException( location.getAbsolutePath() + " contains unexpected data: " + file );
296                     }
297                     file = file.substring( "URI//".length() );
298                     return new File( new URI( file ) );
299                 }
300             }
301             finally
302             {
303                 IOUtil.close( fileInputStream );
304                 IOUtil.close( dataInputStream );
305             }
306         }
307         File projectBase = new File( workspaceLocation, project.getName() );
308         if ( projectBase.isDirectory() )
309         {
310             return projectBase;
311         }
312 
313         return null;
314     }
315 
316     /**
317      * get a value from a dom element.
318      * 
319      * @param element the element to get a value from
320      * @param elementNames the sub elements to get
321      * @param defaultValue teh default value if the value was null or empty
322      * @return the value of the dome element.
323      */
324     private String getValue( Xpp3Dom element, String[] elementNames, String defaultValue )
325     {
326         String value = null;
327         Xpp3Dom dom = element;
328         for ( int index = 0; dom != null && index < elementNames.length; index++ )
329         {
330             dom = dom.getChild( elementNames[index] );
331         }
332         if ( dom != null )
333         {
334             value = dom.getValue();
335         }
336         if ( value == null || value.trim().length() == 0 )
337         {
338             return defaultValue;
339         }
340         else
341         {
342             return value;
343         }
344     }
345 
346     /**
347      * Read the artefact information from the pom in the project location and the eclipse project name from the .project
348      * file.
349      * 
350      * @param projectLocation the location of the project
351      * @param logger the logger to report errors and debug info.
352      * @return an {@link IdeDependency} or null.
353      * @throws FileNotFoundException
354      * @throws XmlPullParserException
355      * @throws IOException
356      */
357     private IdeDependency readArtefact( File projectLocation, Log logger )
358         throws XmlPullParserException, IOException
359     {
360         File projectFile = new File( projectLocation, ".project" );
361         String eclipseProjectName = projectLocation.getName();
362         if ( projectFile.exists() )
363         {
364             Xpp3Dom project = Xpp3DomBuilder.build( new FileReader( projectFile ) );
365             eclipseProjectName = getValue( project, new String[] { "name" }, eclipseProjectName );
366         }
367         File pomFile = new File( projectLocation, "pom.xml" );
368         if ( pomFile.exists() )
369         {
370             Xpp3Dom pom = Xpp3DomBuilder.build( new FileReader( pomFile ) );
371 
372             String artifact = getValue( pom, ReadWorkspaceLocations.ARTEFACT_ID, null );
373             String group =
374                 getValue( pom, ReadWorkspaceLocations.GROUP_ID,
375                           getValue( pom, ReadWorkspaceLocations.PARENT_GROUP_ID, null ) );
376             String version =
377                 getValue( pom, ReadWorkspaceLocations.VERSION,
378                           getValue( pom, ReadWorkspaceLocations.PARENT_VERSION, null ) );
379             String packaging = getValue( pom, ReadWorkspaceLocations.PACKAGING, "jar" );
380 
381             logger.debug( "found workspace artefact " + group + ":" + artifact + ":" + version + " " + packaging + " ("
382                 + eclipseProjectName + ")" + " -> " + projectLocation.getAbsolutePath() );
383             return new IdeDependency( group, artifact, version, packaging, true, false, false, false, false, null,
384                                       packaging, false, null, 0, eclipseProjectName );
385         }
386         else
387         {
388             logger.debug( "ignored workspace project NO pom available " + projectLocation.getAbsolutePath() );
389             return null;
390         }
391     }
392 
393     /* package */Map<String, String> readDefinedServers( WorkspaceConfiguration workspaceConfiguration, Log logger )
394     {
395         Map<String, String> detectedRuntimes = new HashMap<String, String>();
396         if ( workspaceConfiguration.getWorkspaceDirectory() != null )
397         {
398             Xpp3Dom runtimesElement = null;
399             try
400             {
401                 File prefs =
402                     new File( workspaceConfiguration.getWorkspaceDirectory(),
403                               ReadWorkspaceLocations.METADATA_PLUGINS_ORG_ECLIPSE_CORE_RUNTIME_SERVER_PREFS );
404                 if ( prefs.exists() )
405                 {
406                     Properties properties = new Properties();
407                     properties.load( new FileInputStream( prefs ) );
408                     String runtimes =
409                         properties.getProperty( ReadWorkspaceLocations.METADATA_PLUGINS_ORG_ECLIPSE_CORE_RUNTIME_PREFS_RUNTIMES_KEY );
410                     if ( runtimes != null )
411                     {
412                         runtimesElement = Xpp3DomBuilder.build( new StringReader( runtimes ) );
413                     }
414                 }
415             }
416             catch ( Exception e )
417             {
418                 logger.error( "Could not read workspace wtp server runtimes preferences : " + e.getMessage() );
419             }
420 
421             if ( runtimesElement != null )
422             {
423                 Xpp3Dom[] runtimeArray = runtimesElement.getChildren( "runtime" );
424                 for ( int index = 0; runtimeArray != null && index < runtimeArray.length; index++ )
425                 {
426                     String id = runtimeArray[index].getAttribute( "id" );
427                     String name = runtimeArray[index].getAttribute( "name" );
428                     if ( detectedRuntimes.isEmpty() )
429                     {
430                         logger.debug( "Using WTP runtime with id: \"" + id + "\" as default runtime" );
431                         detectedRuntimes.put( "", id );
432                     }
433                     detectedRuntimes.put( id, name );
434                     logger.debug( "Detected WTP runtime with id: \"" + id + "\" and name: \"" + name + "\"" );
435                 }
436             }
437         }
438         return detectedRuntimes;
439     }
440 
441     /**
442      * Read the JRE definition configured in the workspace. They will be put in a HashMap with as key there path and as
443      * value the JRE constant. a second key is included with the JRE version as a key.
444      * 
445      * @param workspaceLocation the workspace location
446      * @param logger the logger to error messages
447      * @return the map with found jre's
448      */
449     private Map<String, String> readAvailableJREs( File workspaceLocation, Log logger )
450     {
451         Map<String, String> jreMap = new HashMap<String, String>();
452         jreMap.put( "1.2", CLASSPATHENTRY_STANDARD + "J2SE-1.2" );
453         jreMap.put( "1.3", CLASSPATHENTRY_STANDARD + "J2SE-1.3" );
454         jreMap.put( "1.4", CLASSPATHENTRY_STANDARD + "J2SE-1.4" );
455         jreMap.put( "1.5", CLASSPATHENTRY_STANDARD + "J2SE-1.5" );
456         jreMap.put( "5", jreMap.get( "1.5" ) );
457         jreMap.put( "1.6", CLASSPATHENTRY_STANDARD + "JavaSE-1.6" );
458         jreMap.put( "6", jreMap.get( "1.6" ) );
459         jreMap.put( "1.7", CLASSPATHENTRY_STANDARD + "JavaSE-1.7" );
460         jreMap.put( "7", jreMap.get( "1.7" ) );
461         jreMap.put( "1.8", CLASSPATHENTRY_STANDARD + "JavaSE-1.8" );
462         jreMap.put( "8", jreMap.get( "1.8" ) );
463 
464         if ( workspaceLocation == null )
465         {
466             return jreMap;
467         }
468 
469         Xpp3Dom vms;
470         try
471         {
472             File prefs =
473                 new File( workspaceLocation,
474                           ReadWorkspaceLocations.METADATA_PLUGINS_ORG_ECLIPSE_CORE_RUNTIME_LAUNCHING_PREFS );
475             if ( !prefs.exists() )
476             {
477                 return null;
478             }
479             Properties properties = new Properties();
480             properties.load( new FileInputStream( prefs ) );
481             vms =
482                 Xpp3DomBuilder.build( new StringReader(
483                                                         properties.getProperty( ReadWorkspaceLocations.METADATA_PLUGINS_ORG_ECLIPSE_CORE_RUNTIME_PREFS_VM_KEY ) ) );
484         }
485         catch ( Exception e )
486         {
487             logger.error( "Could not read workspace JRE preferences", e );
488             return null;
489         }
490         String defaultJRE = vms.getAttribute( "defaultVM" ).trim();
491         Xpp3Dom[] vmTypes = vms.getChildren( "vmType" );
492         for ( Xpp3Dom vmType : vmTypes )
493         {
494             String typeId = vmType.getAttribute( "id" );
495             Xpp3Dom[] vm = vmType.getChildren( "vm" );
496             for ( Xpp3Dom aVm : vm )
497             {
498                 try
499                 {
500                     String path = aVm.getAttribute( "path" );
501                     String name = aVm.getAttribute( "name" );
502                     String vmId = aVm.getAttribute( "id" ).trim();
503                     String classpathEntry =
504                         MessageFormat.format( ReadWorkspaceLocations.CLASSPATHENTRY_FORMAT, typeId, name );
505                     String jrePath = new File( path ).getCanonicalPath();
506                     File rtJarFile = new File( new File( jrePath ), "jre/lib/rt.jar" );
507                     if ( !rtJarFile.exists() )
508                     {
509                         logger.warn( Messages.getString( "EclipsePlugin.invalidvminworkspace", jrePath ) );
510                         continue;
511                     }
512                     JarFile rtJar = new JarFile( rtJarFile );
513                     String version = rtJar.getManifest().getMainAttributes().getValue( "Specification-Version" );
514                     if ( defaultJRE.endsWith( "," + vmId ) )
515                     {
516                         jreMap.put( jrePath, ReadWorkspaceLocations.CLASSPATHENTRY_DEFAULT );
517                         jreMap.put( version, ReadWorkspaceLocations.CLASSPATHENTRY_DEFAULT );
518                         logger.debug( "Default Classpath Container version: " + version + "  location: " + jrePath );
519                     }
520                     else if ( !jreMap.containsKey( jrePath ) )
521                     {
522                         if ( !jreMap.containsKey( version ) )
523                         {
524                             jreMap.put( version, classpathEntry );
525                         }
526                         jreMap.put( jrePath, classpathEntry );
527                         logger.debug( "Additional Classpath Container version: " + version + " " + classpathEntry
528                             + " location: " + jrePath );
529                     }
530                     else
531                     {
532                         logger.debug( "Ignored (duplicated) additional Classpath Container version: " + version + " "
533                             + classpathEntry + " location: " + jrePath );
534                     }
535                 }
536                 catch ( IOException e )
537                 {
538                     logger.warn( "Could not interpret entry: " + aVm.toString() );
539                 }
540             }
541         }
542         return jreMap;
543     }
544 
545     /**
546      * @param workspaceDirectory the directory of the workspace
547      * @param logger logger
548      * @return the physical locations of all workspace projects
549      */
550     public List<File> readProjectLocations( File workspaceDirectory, Log logger )
551     {
552         List<File> projectLocations = new ArrayList<File>();
553         File projectsDirectory =
554             new File( workspaceDirectory, ReadWorkspaceLocations.METADATA_PLUGINS_ORG_ECLIPSE_CORE_RESOURCES_PROJECTS );
555 
556         if ( projectsDirectory.exists() )
557         {
558             for ( File project : projectsDirectory.listFiles() )
559             {
560                 if ( project.isDirectory() )
561                 {
562                     try
563                     {
564                         File projectLocation = getProjectLocation( workspaceDirectory, project );
565                         if ( projectLocation != null )
566                         {
567                             projectLocations.add( projectLocation );
568                         }
569                     }
570                     catch ( Exception e )
571                     {
572                         logger.warn( "could not read workspace project:" + project, e );
573                     }
574                 }
575             }
576         }
577 
578         return projectLocations;
579     }
580     
581     /**
582      * Scan the eclipse workspace and create a array with {@link IdeDependency} for all found artifacts.
583      * 
584      * @param workspaceConfiguration the location of the eclipse workspace.
585      * @param logger the logger to report errors and debug info.
586      */
587     private void readWorkspace( WorkspaceConfiguration workspaceConfiguration, Log logger )
588     {
589         List<IdeDependency> dependencies = new ArrayList<IdeDependency>();
590         File workspaceDirectory = workspaceConfiguration.getWorkspaceDirectory();
591         if ( workspaceDirectory != null )
592         {
593             for ( File projectLocation : readProjectLocations( workspaceDirectory, logger ) )
594             {
595                 try
596                 {
597                     logger.debug( "read workpsace project " + projectLocation );
598                     IdeDependency ideDependency = readArtefact( projectLocation, logger );
599                     if ( ideDependency != null )
600                     {
601                         dependencies.add( ideDependency );
602                     }
603                 }
604                 catch ( Exception e )
605                 {
606                     logger.warn( "could not read workspace project from:" + projectLocation, e );
607                 }
608             }
609         }
610         logger.debug( dependencies.size() + " from workspace " + workspaceDirectory );
611         workspaceConfiguration.setWorkspaceArtefacts( dependencies.toArray( new IdeDependency[dependencies.size()] ) );
612     }
613 }