You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by be...@apache.org on 2009/07/01 00:36:31 UTC

svn commit: r789993 - in /maven/components/trunk/maven-core/src: main/java/org/apache/maven/ main/java/org/apache/maven/execution/ main/java/org/apache/maven/lifecycle/ main/java/org/apache/maven/plugin/ main/java/org/apache/maven/project/ test/java/or...

Author: bentmann
Date: Tue Jun 30 22:36:30 2009
New Revision: 789993

URL: http://svn.apache.org/viewvc?rev=789993&view=rev
Log:
[MNG-4224] maven lifecycle participant
Submitted by: Igor Fedorenko

Added:
    maven/components/trunk/maven-core/src/main/java/org/apache/maven/AbstractMavenLifecycleParticipant.java   (with props)
    maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenLifecycleParticipantTest.java   (with props)
    maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/   (with props)
    maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/   (with props)
    maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/pom.xml   (with props)
Modified:
    maven/components/trunk/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
    maven/components/trunk/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java
    maven/components/trunk/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java
    maven/components/trunk/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginManager.java
    maven/components/trunk/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
    maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenTest.java

Added: maven/components/trunk/maven-core/src/main/java/org/apache/maven/AbstractMavenLifecycleParticipant.java
URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/main/java/org/apache/maven/AbstractMavenLifecycleParticipant.java?rev=789993&view=auto
==============================================================================
--- maven/components/trunk/maven-core/src/main/java/org/apache/maven/AbstractMavenLifecycleParticipant.java (added)
+++ maven/components/trunk/maven-core/src/main/java/org/apache/maven/AbstractMavenLifecycleParticipant.java Tue Jun 30 22:36:30 2009
@@ -0,0 +1,54 @@
+package org.apache.maven;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+
+import org.apache.maven.execution.MavenSession;
+
+/**
+ * Allows core extensions to participate in build lifecycle. 
+ * 
+ * All callback methods (will) follow beforeXXX/afterXXX naming pattern to 
+ * indicate at what lifecycle point it is being called.  
+ * 
+ */
+public abstract class AbstractMavenLifecycleParticipant
+{
+
+    /**
+     * Invoked after all MavenProject instances have been created.
+     * 
+     * This callback is intended to allow extensions to manipulate MavenProjects
+     * before they are sorted and actual build execution starts.
+     */
+    public void afterProjectsRead( MavenSession session ) throws MavenExecutionException
+    {
+        // do nothing
+    }
+
+    /**
+     * Invoked after MavenSession instance has been created. 
+     * 
+     * This callback is intended to allow extensions to inject execution properties,
+     * activate profiles and perform similar tasks that affect MavenProject
+     * instance construction.
+     */
+    public void afterSessionStart( MavenSession session ) throws MavenExecutionException
+    {
+        // do nothing
+    }
+
+}

Propchange: maven/components/trunk/maven-core/src/main/java/org/apache/maven/AbstractMavenLifecycleParticipant.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/components/trunk/maven-core/src/main/java/org/apache/maven/AbstractMavenLifecycleParticipant.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Modified: maven/components/trunk/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/main/java/org/apache/maven/DefaultMaven.java?rev=789993&r1=789992&r2=789993&view=diff
==============================================================================
--- maven/components/trunk/maven-core/src/main/java/org/apache/maven/DefaultMaven.java (original)
+++ maven/components/trunk/maven-core/src/main/java/org/apache/maven/DefaultMaven.java Tue Jun 30 22:36:30 2009
@@ -22,7 +22,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -46,6 +45,7 @@
 import org.codehaus.plexus.PlexusContainer;
 import org.codehaus.plexus.component.annotations.Component;
 import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
 import org.codehaus.plexus.logging.Logger;
 import org.codehaus.plexus.util.Os;
 import org.codehaus.plexus.util.StringUtils;
@@ -86,13 +86,24 @@
         DelegatingLocalArtifactRepository delegatingLocalArtifactRepository = new DelegatingLocalArtifactRepository( request.getLocalRepository() );
         
         request.setLocalRepository( delegatingLocalArtifactRepository );        
-                
-        MavenSession session;
+
+        MavenSession session = new MavenSession( container, request, result);
         
-        Map<String,MavenProject> projects;
+        try
+        {
+            for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants() )
+            {
+                listener.afterSessionStart( session );
+            }
+        }
+        catch ( MavenExecutionException e )
+        {
+            return processResult( result, e );
+        }
 
         //TODO: optimize for the single project or no project
         
+        List<MavenProject> projects;
         try
         {
             projects = getProjectsForMavenReactor( request );
@@ -101,7 +112,7 @@
             if ( projects.isEmpty() )
             {
                 MavenProject project = projectBuilder.buildStandaloneSuperProject( request.getProjectBuildingRequest() ); 
-                projects.put( ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() ), project );
+                projects.add( project );
                 request.setProjectPresent( false );
             }
         }
@@ -113,12 +124,28 @@
         {
             return processResult( result, e );
         }
-        
+
+        session.setProjects( projects );
+
+        try
+        {
+            for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants() )
+            {
+                listener.afterProjectsRead( session );
+            }
+        }
+        catch ( MavenExecutionException e )
+        {
+            return processResult( result, e );
+        }
+
         try
         {                        
-            ProjectSorter projectSorter = new ProjectSorter( projects.values() );
-                                    
-            session = new MavenSession( container, request, result, projectSorter.getSortedProjects() );            
+            ProjectSorter projectSorter = new ProjectSorter( session.getProjects() );
+
+            projects = projectSorter.getSortedProjects();
+
+            session.setProjects( projects );
         }
         catch ( CycleDetectedException e )
         {            
@@ -132,14 +159,20 @@
         {
             return processResult( result, e );
         }
-       
+
         // Desired order of precedence for local artifact repositories
         //
         // Reactor
         // Workspace
         // User Local Repository
-                
-        delegatingLocalArtifactRepository.setBuildReactor( new ReactorArtifactRepository( projects ) );
+        try
+        {
+            delegatingLocalArtifactRepository.setBuildReactor( new ReactorArtifactRepository( getProjectMap( session.getProjects() ) ) );
+        }
+        catch ( MavenExecutionException e )
+        {
+            return processResult( result, e );
+        }
         
         if ( result.hasExceptions() )
         {
@@ -162,6 +195,22 @@
         return result;
     }
 
+    private List<AbstractMavenLifecycleParticipant> getLifecycleParticipants()
+    {
+        // TODO injection of component lists does not work
+        List<AbstractMavenLifecycleParticipant> lifecycleListeners;
+        try
+        {
+            lifecycleListeners = container.lookupList( AbstractMavenLifecycleParticipant.class );
+        }
+        catch ( ComponentLookupException e1 )
+        {
+            // this is just silly, lookupList should return an empty list!
+            lifecycleListeners = new ArrayList<AbstractMavenLifecycleParticipant>();
+        }
+        return lifecycleListeners;
+    }
+
     private MavenExecutionResult processResult( MavenExecutionResult result, Exception e )
     {
         ExceptionHandler handler = new DefaultExceptionHandler();
@@ -175,14 +224,14 @@
         return result;
     }
     
-    protected Map<String,MavenProject> getProjectsForMavenReactor( MavenExecutionRequest request )
+    private List<MavenProject> getProjectsForMavenReactor( MavenExecutionRequest request )
         throws MavenExecutionException, ProjectBuildingException
     {
         // We have no POM file.
         //
         if ( request.getPom() == null || !request.getPom().exists() )
         {
-            return new HashMap<String,MavenProject>();
+            return new ArrayList<MavenProject>();
         }
         
         List<File> files = Arrays.asList( request.getPom().getAbsoluteFile() );
@@ -191,6 +240,12 @@
 
         collectProjects( projects, files, request );
 
+        return projects;
+    }
+
+    private Map<String, MavenProject> getProjectMap( List<MavenProject> projects )
+        throws org.apache.maven.DuplicateProjectException
+    {
         Map<String, MavenProject> index = new LinkedHashMap<String, MavenProject>();
         Map<String, List<File>> collisions = new LinkedHashMap<String, List<File>>();
 

Modified: maven/components/trunk/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java
URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java?rev=789993&r1=789992&r2=789993&view=diff
==============================================================================
--- maven/components/trunk/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java (original)
+++ maven/components/trunk/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java Tue Jun 30 22:36:30 2009
@@ -31,7 +31,6 @@
 import org.apache.maven.project.ProjectBuildingRequest;
 import org.apache.maven.settings.Settings;
 import org.codehaus.plexus.PlexusContainer;
-import org.codehaus.plexus.util.dag.CycleDetectedException;
 
 /**
  * @author Jason van Zyl
@@ -55,25 +54,37 @@
     
     private MavenProject topLevelProject;
     
+    @Deprecated
     public MavenSession( PlexusContainer container, MavenExecutionRequest request, MavenExecutionResult result, MavenProject project )
-        throws CycleDetectedException, DuplicateProjectException
     {
         this( container, request, result, Arrays.asList( new MavenProject[]{ project } ) );        
     }    
 
+    @Deprecated
     public MavenSession( PlexusContainer container, MavenExecutionRequest request, MavenExecutionResult result, List<MavenProject> projects )
-        throws CycleDetectedException, DuplicateProjectException
     {
         this.container = container;
         this.request = request;
         this.result = result;
+        setProjects( projects );     
+    }
+
+    public MavenSession( PlexusContainer container, MavenExecutionRequest request, MavenExecutionResult result )
+    {
+        this.container = container;
+        this.request = request;
+        this.result = result;
+    }
+
+    public void setProjects( List<MavenProject> projects )
+    {
         //TODO: Current for testing classes creating the session
         if ( projects.size() > 0 )
         {
             this.currentProject = projects.get( 0 );
             this.topLevelProject = projects.get(  0 );
         }
-        this.projects = projects;     
+        this.projects = projects;
     }    
         
     @Deprecated

Modified: maven/components/trunk/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java
URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java?rev=789993&r1=789992&r2=789993&view=diff
==============================================================================
--- maven/components/trunk/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java (original)
+++ maven/components/trunk/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java Tue Jun 30 22:36:30 2009
@@ -806,7 +806,10 @@
                 {
                     String phase = goalsForLifecyclePhase.getKey();
                     String goals = goalsForLifecyclePhase.getValue();
-                    parseLifecyclePhaseDefinitions( plugins, phase, goals );
+                    if ( goals != null )
+                    {
+                        parseLifecyclePhaseDefinitions( plugins, phase, goals );
+                    }
                 }
             }
             else if ( lifecycle.getDefaultPhases() != null )

Modified: maven/components/trunk/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginManager.java
URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginManager.java?rev=789993&r1=789992&r2=789993&view=diff
==============================================================================
--- maven/components/trunk/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginManager.java (original)
+++ maven/components/trunk/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginManager.java Tue Jun 30 22:36:30 2009
@@ -222,7 +222,7 @@
         }
     }
 
-    private PluginDescriptor parsebuildPluginDescriptor( InputStream is )
+    public PluginDescriptor parsebuildPluginDescriptor( InputStream is )
         throws IOException, PlexusConfigurationException
     {
         PluginDescriptor pluginDescriptor;

Modified: maven/components/trunk/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/main/java/org/apache/maven/project/MavenProject.java?rev=789993&r1=789992&r2=789993&view=diff
==============================================================================
--- maven/components/trunk/maven-core/src/main/java/org/apache/maven/project/MavenProject.java (original)
+++ maven/components/trunk/maven-core/src/main/java/org/apache/maven/project/MavenProject.java Tue Jun 30 22:36:30 2009
@@ -164,7 +164,9 @@
     private RepositorySystem repositorySystem;
     
     private File parentFile;
-    
+
+    private Map<String, Object> context;
+
     //
 
     public MavenProject()
@@ -1966,4 +1968,41 @@
     {
         return groupId + ":" + artifactId + ":" + version;
     }
+
+    /**
+     * Sets the value of the context value of this project identified
+     * by the given key. If the supplied value is <code>null</code>,
+     * the context value is removed from this project.
+     * 
+     * Context values are intended to allow core extensions to associate
+     * derived state with project instances. 
+     */
+    public void setContextValue( String key, Object value )
+    {
+        if ( context == null )
+        {
+            context = new HashMap<String, Object>();
+        }
+        if ( value != null )
+        {
+            context.put( key, value );
+        }
+        else
+        {
+            context.remove( key );
+        }
+    }
+
+    /**
+     * Returns context value of this project associated with the given key 
+     * or null if this project has no such value. 
+     */
+    public Object getContextValue( String key )
+    {
+        if ( context == null )
+        {
+            return null;
+        }
+        return context.get( key );
+    }
 }

Added: maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenLifecycleParticipantTest.java
URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenLifecycleParticipantTest.java?rev=789993&view=auto
==============================================================================
--- maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenLifecycleParticipantTest.java (added)
+++ maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenLifecycleParticipantTest.java Tue Jun 30 22:36:30 2009
@@ -0,0 +1,111 @@
+package org.apache.maven;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.execution.MavenExecutionResult;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.repository.ComponentDescriptor;
+
+public class MavenLifecycleParticipantTest
+    extends AbstractCoreMavenComponentTestCase
+{
+
+    private static final String INJECTED_ARTIFACT_ID = "injected";
+
+    public static class InjectDependencyLifecycleListener
+        extends AbstractMavenLifecycleParticipant
+    {
+
+        @Override
+        public void afterProjectsRead( MavenSession session )
+        {
+            MavenProject project = session.getProjects().get( 0 );
+
+            Dependency dependency = new Dependency();
+            dependency.setArtifactId( INJECTED_ARTIFACT_ID );
+            dependency.setGroupId( "foo" );
+            dependency.setVersion( "1.2.3" );
+            dependency.setScope( "system" );
+            try
+            {
+                dependency.setSystemPath( new File(
+                                                    "src/test/projects/lifecycle-executor/project-with-additional-lifecycle-elements/pom.xml" ).getCanonicalPath() );
+            }
+            catch ( IOException e )
+            {
+                throw new RuntimeException( e );
+            }
+
+            project.getModel().addDependency( dependency );
+        }
+
+        @Override
+        public void afterSessionStart( MavenSession session )
+        {
+            session.getExecutionProperties().setProperty( "injected", "bar" );
+        }
+
+    }
+
+    @Override
+    protected void setupContainer()
+    {
+        super.setupContainer();
+    }
+
+    @Override
+    protected String getProjectsDirectory()
+    {
+        return "src/test/projects/lifecycle-listener";
+    }
+
+    public void testDependencyInjection()
+        throws Exception
+    {
+        PlexusContainer container = getContainer();
+
+        ComponentDescriptor cd =
+            new ComponentDescriptor( InjectDependencyLifecycleListener.class, container.getContainerRealm() );
+        cd.setRoleClass( AbstractMavenLifecycleParticipant.class );
+        container.addComponentDescriptor( cd );
+
+        Maven maven = container.lookup( Maven.class );
+        File pom = getProject( "lifecycle-listener-dependency-injection" );
+        MavenExecutionRequest request = createMavenExecutionRequest( pom );
+        MavenExecutionResult result = maven.execute( request );
+
+        assertFalse( result.hasExceptions() );
+
+        MavenProject project = result.getProject();
+        
+        assertEquals( "bar", project.getProperties().getProperty( "foo" ) );
+
+        ArrayList<Artifact> artifacts = new ArrayList<Artifact>( project.getArtifacts() );
+
+        assertEquals( 1, artifacts.size() );
+        assertEquals( INJECTED_ARTIFACT_ID, artifacts.get( 0 ).getArtifactId() );
+    }
+}

Propchange: maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenLifecycleParticipantTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenLifecycleParticipantTest.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Modified: maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenTest.java
URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenTest.java?rev=789993&r1=789992&r2=789993&view=diff
==============================================================================
--- maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenTest.java (original)
+++ maven/components/trunk/maven-core/src/test/java/org/apache/maven/MavenTest.java Tue Jun 30 22:36:30 2009
@@ -6,7 +6,6 @@
 import org.apache.maven.exception.ExceptionSummary;
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenExecutionResult;
-import org.apache.maven.execution.MavenSession;
 import org.codehaus.plexus.component.annotations.Requirement;
 
 public class MavenTest

Propchange: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/
------------------------------------------------------------------------------
    bugtraq:label = Enter issue ID:

Propchange: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/
------------------------------------------------------------------------------
    bugtraq:message = Issue id: %BUGID%

Propchange: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/
------------------------------------------------------------------------------
    bugtraq:number = false

Propchange: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/
------------------------------------------------------------------------------
    bugtraq:url = http://jira.codehaus.org/browse/%BUGID%

Propchange: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/
------------------------------------------------------------------------------
    bugtraq:label = Enter issue ID:

Propchange: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/
------------------------------------------------------------------------------
    bugtraq:message = Issue id: %BUGID%

Propchange: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/
------------------------------------------------------------------------------
    bugtraq:number = false

Propchange: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/
------------------------------------------------------------------------------
    bugtraq:url = http://jira.codehaus.org/browse/%BUGID%

Added: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/pom.xml
URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/pom.xml?rev=789993&view=auto
==============================================================================
--- maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/pom.xml (added)
+++ maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/pom.xml Tue Jun 30 22:36:30 2009
@@ -0,0 +1,12 @@
+<project>
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.lifecycle-listener.test</groupId>
+  <artifactId>simple</artifactId>
+  <version>1.0</version>
+  
+  <properties>
+    <foo>${injected}</foo>
+  </properties>
+
+</project>

Propchange: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/pom.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/components/trunk/maven-core/src/test/projects/lifecycle-listener/lifecycle-listener-dependency-injection/pom.xml
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision