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.cling.invoker.mvn.resident;
20  
21  import java.util.ArrayList;
22  import java.util.concurrent.ConcurrentHashMap;
23  import java.util.function.Consumer;
24  
25  import org.apache.maven.api.annotations.Nullable;
26  import org.apache.maven.api.cli.InvokerException;
27  import org.apache.maven.api.cli.InvokerRequest;
28  import org.apache.maven.api.cli.mvn.MavenOptions;
29  import org.apache.maven.api.services.Lookup;
30  import org.apache.maven.cling.invoker.LookupContext;
31  import org.apache.maven.cling.invoker.mvn.MavenContext;
32  import org.apache.maven.cling.invoker.mvn.MavenInvoker;
33  
34  /**
35   * Resident invoker implementation, specialization of Maven Invoker, but keeps Maven instance resident. This implies, that
36   * things like environment, system properties, extensions etc. are loaded only once. It is caller duty to ensure
37   * that subsequent call is right for the resident instance (ie no env change or different extension needed).
38   * This implementation "pre-populates" MavenContext with pre-existing stuff (except for very first call)
39   * and does not let DI container to be closed.
40   */
41  public class ResidentMavenInvoker extends MavenInvoker {
42  
43      private final ConcurrentHashMap<String, MavenContext> residentContext;
44  
45      public ResidentMavenInvoker(Lookup protoLookup, @Nullable Consumer<LookupContext> contextConsumer) {
46          super(protoLookup, contextConsumer);
47          this.residentContext = new ConcurrentHashMap<>();
48      }
49  
50      @Override
51      public void close() throws InvokerException {
52          ArrayList<Exception> exceptions = new ArrayList<>();
53          for (MavenContext context : residentContext.values()) {
54              try {
55                  context.doCloseContainer();
56              } catch (Exception e) {
57                  exceptions.add(e);
58              }
59          }
60          if (!exceptions.isEmpty()) {
61              InvokerException exception = new InvokerException("Could not cleanly shut down context pool");
62              exceptions.forEach(exception::addSuppressed);
63              throw exception;
64          }
65      }
66  
67      @Override
68      protected MavenContext createContext(InvokerRequest invokerRequest) {
69          // TODO: in a moment Maven stop pushing user properties to system properties (and maybe something more)
70          // and allow multiple instances per JVM, this may become a pool? derive key based in invokerRequest?
71          MavenContext result = residentContext.computeIfAbsent(
72                  "resident",
73                  k -> new MavenContext(invokerRequest, false, (MavenOptions)
74                          invokerRequest.options().orElse(null)));
75          return copyIfDifferent(result, invokerRequest);
76      }
77  
78      protected MavenContext copyIfDifferent(MavenContext mavenContext, InvokerRequest invokerRequest) {
79          if (invokerRequest == mavenContext.invokerRequest) {
80              return mavenContext;
81          }
82          MavenContext shadow = new MavenContext(
83                  invokerRequest, false, (MavenOptions) invokerRequest.options().orElse(null));
84  
85          // we carry over only "resident" things
86          shadow.containerCapsule = mavenContext.containerCapsule;
87          shadow.lookup = mavenContext.lookup;
88          shadow.eventSpyDispatcher = mavenContext.eventSpyDispatcher;
89          shadow.maven = mavenContext.maven;
90  
91          return shadow;
92      }
93  }