View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.buildcache;
20  
21  import javax.annotation.Nonnull;
22  import javax.annotation.PostConstruct;
23  import javax.inject.Inject;
24  import javax.inject.Named;
25  
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.Objects;
29  import java.util.concurrent.ConcurrentHashMap;
30  import java.util.concurrent.ConcurrentMap;
31  import java.util.stream.Collectors;
32  
33  import org.apache.maven.SessionScoped;
34  import org.apache.maven.buildcache.xml.Build;
35  import org.apache.maven.execution.AbstractExecutionListener;
36  import org.apache.maven.execution.ExecutionEvent;
37  import org.apache.maven.execution.MavenExecutionRequest;
38  import org.apache.maven.execution.MavenSession;
39  import org.apache.maven.lifecycle.DefaultLifecycles;
40  import org.apache.maven.lifecycle.Lifecycle;
41  import org.apache.maven.plugin.MojoExecution;
42  import org.apache.maven.project.MavenProject;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  @SessionScoped
47  @Named
48  public class LifecyclePhasesHelper extends AbstractExecutionListener {
49  
50      private static final Logger LOGGER = LoggerFactory.getLogger(LifecyclePhasesHelper.class);
51  
52      private final MavenSession session;
53      private final DefaultLifecycles defaultLifecycles;
54      private final List<String> phases;
55      private final String lastCleanPhase;
56  
57      private final ConcurrentMap<MavenProject, MojoExecution> forkedProjectToOrigin = new ConcurrentHashMap<>();
58  
59      @Inject
60      public LifecyclePhasesHelper(
61              MavenSession session, DefaultLifecycles defaultLifecycles, @Named("clean") Lifecycle cleanLifecycle) {
62          this.session = session;
63          this.defaultLifecycles = Objects.requireNonNull(defaultLifecycles);
64          this.phases = defaultLifecycles.getLifeCycles().stream()
65                  .flatMap(lf -> lf.getPhases().stream())
66                  .collect(Collectors.toList());
67          this.lastCleanPhase = CacheUtils.getLast(cleanLifecycle.getPhases());
68      }
69  
70      @PostConstruct
71      public void init() {
72          MavenExecutionRequest request = session.getRequest();
73          ChainedListener lifecycleListener = new ChainedListener(request.getExecutionListener());
74          lifecycleListener.chainListener(this);
75          request.setExecutionListener(lifecycleListener);
76      }
77  
78      @Override
79      public void forkedProjectStarted(ExecutionEvent event) {
80          LOGGER.debug(
81                  "Started forked project. Project: {}, instance: {}, originating mojo: {}",
82                  event.getProject(),
83                  System.identityHashCode(event.getProject()),
84                  event.getMojoExecution());
85          forkedProjectToOrigin.put(event.getProject(), event.getMojoExecution());
86      }
87  
88      @Override
89      public void forkedProjectSucceeded(ExecutionEvent event) {
90          LOGGER.debug(
91                  "Finished forked project. Project: {}, instance: {}",
92                  event.getProject(),
93                  System.identityHashCode(event.getProject()));
94          forkedProjectToOrigin.remove(event.getProject(), event.getMojoExecution());
95      }
96  
97      @Override
98      public void forkedProjectFailed(ExecutionEvent event) {
99          LOGGER.debug(
100                 "Finished forked project. Project: {}, instance: {}",
101                 event.getProject(),
102                 System.identityHashCode(event.getProject()));
103         forkedProjectToOrigin.remove(event.getProject(), event.getMojoExecution());
104     }
105 
106     @Nonnull
107     public String resolveHighestLifecyclePhase(MavenProject project, List<MojoExecution> mojoExecutions) {
108         return resolveMojoExecutionLifecyclePhase(project, CacheUtils.getLast(mojoExecutions));
109     }
110 
111     /**
112      * Check if the given phase is later than the clean lifecycle.
113      */
114     public boolean isLaterPhaseThanClean(String phase) {
115         return isLaterPhase(phase, lastCleanPhase);
116     }
117 
118     public boolean isLaterPhaseThanBuild(String phase, Build build) {
119         return isLaterPhase(phase, build.getHighestCompletedGoal());
120     }
121 
122     /**
123      * Check if the given phase is later than the other in maven lifecycle.
124      * Example: isLaterPhase("install", "clean") returns true;
125      */
126     public boolean isLaterPhase(String phase, String other) {
127         if (!phases.contains(phase)) {
128             throw new IllegalArgumentException("Unsupported phase: " + phase);
129         }
130         if (!phases.contains(other)) {
131             throw new IllegalArgumentException("Unsupported phase: " + other);
132         }
133 
134         return phases.indexOf(phase) > phases.indexOf(other);
135     }
136 
137     /**
138      * Computes the list of mojos executions in the clean phase
139      */
140     public List<MojoExecution> getCleanSegment(MavenProject project, List<MojoExecution> mojoExecutions) {
141         List<MojoExecution> list = new ArrayList<>(mojoExecutions.size());
142         for (MojoExecution mojoExecution : mojoExecutions) {
143             String lifecyclePhase = resolveMojoExecutionLifecyclePhase(project, mojoExecution);
144 
145             if (isLaterPhaseThanClean(lifecyclePhase)) {
146                 break;
147             }
148             list.add(mojoExecution);
149         }
150         return list;
151     }
152 
153     /**
154      * Resolves lifecycle phase of a given mojo forks aware
155      *
156      * @param  project       - project context
157      * @param  mojoExecution - mojo to resolve lifecycle for
158      * @return               phase
159      */
160     private String resolveMojoExecutionLifecyclePhase(MavenProject project, MojoExecution mojoExecution) {
161 
162         MojoExecution forkOrigin = forkedProjectToOrigin.get(project);
163 
164         // if forked, take originating mojo as a lifecycle phase source
165         if (forkOrigin == null) {
166             return mojoExecution.getLifecyclePhase();
167         } else {
168             if (LOGGER.isDebugEnabled()) {
169                 LOGGER.debug(
170                         "Mojo execution {} is forked, returning phase {} from originating mojo {}",
171                         CacheUtils.mojoExecutionKey(mojoExecution),
172                         forkOrigin.getLifecyclePhase(),
173                         CacheUtils.mojoExecutionKey(forkOrigin));
174             }
175             return forkOrigin.getLifecyclePhase();
176         }
177     }
178 
179     /**
180      * Computes the list of mojos executions that are cached.
181      */
182     public List<MojoExecution> getCachedSegment(MavenProject project, List<MojoExecution> mojoExecutions, Build build) {
183         List<MojoExecution> list = new ArrayList<>(mojoExecutions.size());
184         for (MojoExecution mojoExecution : mojoExecutions) {
185             // if forked, take originating mojo as a lifecycle phase source
186             String lifecyclePhase = resolveMojoExecutionLifecyclePhase(project, mojoExecution);
187 
188             if (!isLaterPhaseThanClean(lifecyclePhase)) {
189                 continue;
190             }
191             if (isLaterPhaseThanBuild(lifecyclePhase, build)) {
192                 break;
193             }
194             list.add(mojoExecution);
195         }
196         return list;
197     }
198 
199     /**
200      * Computes the list of mojos executions that will have to be executed after cache restoration.
201      */
202     public List<MojoExecution> getPostCachedSegment(
203             MavenProject project, List<MojoExecution> mojoExecutions, Build build) {
204         List<MojoExecution> list = new ArrayList<>(mojoExecutions.size());
205         for (MojoExecution mojoExecution : mojoExecutions) {
206 
207             // if forked, take originating mojo as a lifecycle phase source
208             String lifecyclePhase = resolveMojoExecutionLifecyclePhase(project, mojoExecution);
209 
210             if (isLaterPhaseThanBuild(lifecyclePhase, build)) {
211                 list.add(mojoExecution);
212             }
213         }
214         return list;
215     }
216 
217     public boolean isForkedProject(MavenProject project) {
218         return forkedProjectToOrigin.containsKey(project);
219     }
220 }