├── .gitignore ├── LICENSE ├── README.md ├── launch └── build_cdi-utils [clean install].launch ├── pom.xml └── src ├── main └── java │ └── com │ └── itemis │ └── maven │ └── plugins │ └── cdi │ ├── AbstractCDIMojo.java │ ├── CDIMojoProcessingStep.java │ ├── ExecutionContext.java │ ├── annotations │ ├── MojoInject.java │ ├── MojoProduces.java │ ├── ProcessingStep.java │ └── RollbackOnError.java │ ├── internal │ ├── beans │ │ ├── CdiBeanWrapper.java │ │ └── CdiProducerBean.java │ └── util │ │ ├── CDIUtil.java │ │ ├── MavenUtil.java │ │ └── workflow │ │ ├── ParallelWorkflowStep.java │ │ ├── ProcessingWorkflow.java │ │ ├── SimpleWorkflowStep.java │ │ ├── WorkflowConstants.java │ │ ├── WorkflowExecutor.java │ │ ├── WorkflowStep.java │ │ ├── WorkflowUtil.java │ │ └── WorkflowValidator.java │ └── logging │ ├── Logger.java │ └── MavenLogWrapper.java └── test ├── java └── com │ └── itemis │ └── maven │ └── plugins │ └── cdi │ └── util │ ├── WorkflowUtilTest.java │ └── WorkflowValidatorTest.java └── resources └── workflows ├── invalid ├── try-finally-noFinallyBlock ├── try-finally-noTryBlockClosing └── try-finally_noTryBlockOpening ├── parallel ├── parallel_data ├── sequential ├── sequential_data ├── sequential_qualifiers ├── try-finally └── try-finally_complex /.gitignore: -------------------------------------------------------------------------------- 1 | #maven excludes 2 | target/ 3 | 4 | #eclipse excludes 5 | .classpath 6 | .project 7 | .settings/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' 19 | from a Contributor if it was added to the Program by such Contributor 20 | itself or anyone acting on such Contributor's behalf. Contributions do not 21 | include additions to the Program which: (i) are separate modules of 22 | software distributed in conjunction with the Program under their own 23 | license agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this 32 | Agreement. 33 | 34 | "Recipient" means anyone who receives the Program under this Agreement, 35 | including all Contributors. 36 | 37 | 2. GRANT OF RIGHTS 38 | a) Subject to the terms of this Agreement, each Contributor hereby grants 39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 40 | reproduce, prepare derivative works of, publicly display, publicly 41 | perform, distribute and sublicense the Contribution of such Contributor, 42 | if any, and such derivative works, in source code and object code form. 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | c) Recipient understands that although each Contributor grants the licenses 54 | to its Contributions set forth herein, no assurances are provided by any 55 | Contributor that the Program does not infringe the patent or other 56 | intellectual property rights of any other entity. Each Contributor 57 | disclaims any liability to Recipient for claims brought by any other 58 | entity based on infringement of intellectual property rights or 59 | otherwise. As a condition to exercising the rights and licenses granted 60 | hereunder, each Recipient hereby assumes sole responsibility to secure 61 | any other intellectual property rights needed, if any. For example, if a 62 | third party patent license is required to allow Recipient to distribute 63 | the Program, it is Recipient's responsibility to acquire that license 64 | before distributing the Program. 65 | d) Each Contributor represents that to its knowledge it has sufficient 66 | copyright rights in its Contribution, if any, to grant the copyright 67 | license set forth in this Agreement. 68 | 69 | 3. REQUIREMENTS 70 | 71 | A Contributor may choose to distribute the Program in object code form under 72 | its own license agreement, provided that: 73 | 74 | a) it complies with the terms and conditions of this Agreement; and 75 | b) its license agreement: 76 | i) effectively disclaims on behalf of all Contributors all warranties 77 | and conditions, express and implied, including warranties or 78 | conditions of title and non-infringement, and implied warranties or 79 | conditions of merchantability and fitness for a particular purpose; 80 | ii) effectively excludes on behalf of all Contributors all liability for 81 | damages, including direct, indirect, special, incidental and 82 | consequential damages, such as lost profits; 83 | iii) states that any provisions which differ from this Agreement are 84 | offered by that Contributor alone and not by any other party; and 85 | iv) states that source code for the Program is available from such 86 | Contributor, and informs licensees how to obtain it in a reasonable 87 | manner on or through a medium customarily used for software exchange. 88 | 89 | When the Program is made available in source code form: 90 | 91 | a) it must be made available under this Agreement; and 92 | b) a copy of this Agreement must be included with each copy of the Program. 93 | Contributors may not remove or alter any copyright notices contained 94 | within the Program. 95 | 96 | Each Contributor must identify itself as the originator of its Contribution, 97 | if 98 | any, in a manner that reasonably allows subsequent Recipients to identify the 99 | originator of the Contribution. 100 | 101 | 4. COMMERCIAL DISTRIBUTION 102 | 103 | Commercial distributors of software may accept certain responsibilities with 104 | respect to end users, business partners and the like. While this license is 105 | intended to facilitate the commercial use of the Program, the Contributor who 106 | includes the Program in a commercial product offering should do so in a manner 107 | which does not create potential liability for other Contributors. Therefore, 108 | if a Contributor includes the Program in a commercial product offering, such 109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 110 | every other Contributor ("Indemnified Contributor") against any losses, 111 | damages and costs (collectively "Losses") arising from claims, lawsuits and 112 | other legal actions brought by a third party against the Indemnified 113 | Contributor to the extent caused by the acts or omissions of such Commercial 114 | Contributor in connection with its distribution of the Program in a commercial 115 | product offering. The obligations in this section do not apply to any claims 116 | or Losses relating to any actual or alleged intellectual property 117 | infringement. In order to qualify, an Indemnified Contributor must: 118 | a) promptly notify the Commercial Contributor in writing of such claim, and 119 | b) allow the Commercial Contributor to control, and cooperate with the 120 | Commercial Contributor in, the defense and any related settlement 121 | negotiations. The Indemnified Contributor may participate in any such claim at 122 | its own expense. 123 | 124 | For example, a Contributor might include the Program in a commercial product 125 | offering, Product X. That Contributor is then a Commercial Contributor. If 126 | that Commercial Contributor then makes performance claims, or offers 127 | warranties related to Product X, those performance claims and warranties are 128 | such Commercial Contributor's responsibility alone. Under this section, the 129 | Commercial Contributor would have to defend claims against the other 130 | Contributors related to those performance claims and warranties, and if a 131 | court requires any other Contributor to pay any damages as a result, the 132 | Commercial Contributor must pay those damages. 133 | 134 | 5. NO WARRANTY 135 | 136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 140 | Recipient is solely responsible for determining the appropriateness of using 141 | and distributing the Program and assumes all risks associated with its 142 | exercise of rights under this Agreement , including but not limited to the 143 | risks and costs of program errors, compliance with applicable laws, damage to 144 | or loss of data, programs or equipment, and unavailability or interruption of 145 | operations. 146 | 147 | 6. DISCLAIMER OF LIABILITY 148 | 149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 156 | OF SUCH DAMAGES. 157 | 158 | 7. GENERAL 159 | 160 | If any provision of this Agreement is invalid or unenforceable under 161 | applicable law, it shall not affect the validity or enforceability of the 162 | remainder of the terms of this Agreement, and without further action by the 163 | parties hereto, such provision shall be reformed to the minimum extent 164 | necessary to make such provision valid and enforceable. 165 | 166 | If Recipient institutes patent litigation against any entity (including a 167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 168 | (excluding combinations of the Program with other software or hardware) 169 | infringes such Recipient's patent(s), then such Recipient's rights granted 170 | under Section 2(b) shall terminate as of the date such litigation is filed. 171 | 172 | All Recipient's rights under this Agreement shall terminate if it fails to 173 | comply with any of the material terms or conditions of this Agreement and does 174 | not cure such failure in a reasonable period of time after becoming aware of 175 | such noncompliance. If all Recipient's rights under this Agreement terminate, 176 | Recipient agrees to cease use and distribution of the Program as soon as 177 | reasonably practicable. However, Recipient's obligations under this Agreement 178 | and any licenses granted by Recipient relating to the Program shall continue 179 | and survive. 180 | 181 | Everyone is permitted to copy and distribute copies of this Agreement, but in 182 | order to avoid inconsistency the Agreement is copyrighted and may only be 183 | modified in the following manner. The Agreement Steward reserves the right to 184 | publish new versions (including revisions) of this Agreement from time to 185 | time. No one other than the Agreement Steward has the right to modify this 186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 187 | Eclipse Foundation may assign the responsibility to serve as the Agreement 188 | Steward to a suitable separate entity. Each new version of the Agreement will 189 | be given a distinguishing version number. The Program (including 190 | Contributions) may always be distributed subject to the version of the 191 | Agreement under which it was received. In addition, after a new version of the 192 | Agreement is published, Contributor may elect to distribute the Program 193 | (including its Contributions) under the new version. Except as expressly 194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 195 | licenses to the intellectual property of any Contributor under this Agreement, 196 | whether expressly, by implication, estoppel or otherwise. All rights in the 197 | Program not expressly granted under this Agreement are reserved. 198 | 199 | This Agreement is governed by the laws of the State of New York and the 200 | intellectual property laws of the United States of America. No party to this 201 | Agreement will bring a legal action under this Agreement more than one year 202 | after the cause of action arose. Each party waives its rights to a jury trial in 203 | any resulting litigation. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CDI-based Dependency Injection for Maven Plugin Development 2 | =========================================================== 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.itemis.maven.plugins/cdi-plugin-utils/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.itemis.maven.plugins/cdi-plugin-utils) 4 | 5 | This small library enables the usage of CDI-based dependency injection in Apache Maven plugins which changes the way of implementing Maven plugins fundamentally. 6 | 7 | 8 | Requirements 9 | ------------ 10 | * JDK 1.7 or higher 11 | * Apache Maven 3.x 12 | 13 | 14 | The Idea Behind It 15 | ------------------ 16 | The explicit desire for dependency injection (DI) in Maven plugins came up during the development of the [Unleash Maven Plugin](https://github.com/shillner/unleash-maven-plugin/). There it was necessary to dynamically inject an implementation of the ScmProvider interface into the provider registry that isn't even known at compile time. A second wish was to simply add those implementations as plugin dependencies when configuring the plugin for your project. 17 | 18 | Since an examination of Maven's DI capabilities did not yield any satisfactory results, this project was brought to life. First there was only the need to enable DI for Maven plugins, without any technological preference. But fast the requirements became more concrete and the choice fell on CDI based on its reference implementation Weld. Here are some of the core requirements that led to the current concepts and implementation: 19 | 20 | * DI in general to decouple components 21 | * Classpath-based autowiring of components 22 | * Distributed feature implementation without having to grind parameters through hundreds of classes 23 | * Ensuring extensibility of the plugins based on this library 24 | * Safe execution of the various processing steps of the plugin with implicit rollback in case of an error 25 | 26 | 27 | The Core Concepts 28 | ----------------- 29 | * CDI-based dependency injection implemented using [Weld SE](https://docs.jboss.org/weld/reference/latest/en-US/html/environments.html#_java_se) 30 | * @Inject 31 | * Qualifiers, Alternatives 32 | * Producers 33 | * @PostConstruct, @PreDestroy 34 | * Events 35 | * Classpath-based autowiring 36 | * The plugin's classpath is automatically scanned for beans (plugin and plugin dependencies) 37 | * No need for declaring beans in any kind of descriptor or manually bind or wire beans 38 | * Workflow-based architecture 39 | * Plugins define a default workflow for each Mojo 40 | * Workflow consisting of several processing steps which results in much smaller and clearer feature implementations 41 | * Safe workflow processing 42 | * The plugin processes the workflow step by step 43 | * Steps can implement one or more rollback methods that are called under certain circumstances 44 | * If any workflow step fails with an exception all processed steps are rolled-back in their reverse order 45 | * Each step only needs to rollback its own changes 46 | * Extensibility by design 47 | * Classpath scanning enables you to add more processing steps or other implementations to the plugin dependencies 48 | * Overriding of the default workflow of a Mojo makes it possible to redefine the workflow, f.i. when embedding new steps 49 | 50 | 51 | Further Information 52 | ------------------- 53 | For more detailed information about how to implement Maven plugins using this library please refer to the [Project Wiki](https://github.com/shillner/maven-cdi-plugin-utils/wiki). There all concepts and their implementation as well as the general usage is explained in detail. 54 | 55 | A reference plugin that bases on this library is available here: [Unleash Maven Plugin](https://github.com/shillner/unleash-maven-plugin/) 56 | This plugin provides f.i. an SCM provider API that is implemented in several external projects such as [Unleash SCM Provider for Git](https://github.com/shillner/unleash-scm-provider-git). These provider implementations can then be added to the plugin dependencies in order to support other SCM types during processing. 57 | 58 | A further project is available here: [Maven CDI Processing hooks](https://github.com/shillner/maven-cdi-plugin-hooks) 59 | This project provides some additional processing step implementations that can be used to extend processing workflows by simply adding the library to the plugin dependencies and overriding the processing workflow. 60 | -------------------------------------------------------------------------------- /launch/build_cdi-utils [clean install].launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | com.itemis 6 | org-parent 7 | 1 8 | 9 | 10 | com.itemis.maven.plugins 11 | cdi-plugin-utils 12 | 3.4.1-SNAPSHOT 13 | 14 | CDI Plugin Utilities 15 | Provides an abstract Mojo that enables CDI-based dependency injection for Maven Plugins. 16 | https://github.com/shillner/maven-cdi-plugin-utils 17 | 2016 18 | 19 | 20 | 21 | shillner 22 | Stanley Hillner 23 | itemis AG 24 | https://itemis.com/ 25 | 1 26 | 27 | 28 | 29 | 30 | scm:git:https://github.com/shillner/maven-cdi-plugin-utils.git 31 | https://github.com/shillner/maven-cdi-plugin-utils 32 | HEAD 33 | 34 | 35 | 36 | GitHub 37 | https://github.com/shillner/maven-cdi-plugin-utils/issues 38 | 39 | 40 | 41 | 3.2.1 42 | 43 | 44 | 45 | 1.0.2.v20150114 46 | 0.2.5 47 | 1.2 48 | 19.0 49 | 1.7 50 | 1 51 | 4.12 52 | 1.10.3 53 | 3.2.1 54 | 3.4 55 | 1.7.21 56 | 2.3.3.Final 57 | 58 | 59 | 60 | 61 | org.eclipse.aether 62 | aether-api 63 | ${version.aether} 64 | 65 | 66 | org.eclipse.aether 67 | aether-impl 68 | ${version.aether} 69 | 70 | 71 | de.vandermeer 72 | asciitable 73 | ${version.asciitable} 74 | 75 | 76 | javax.enterprise 77 | cdi-api 78 | ${version.cdi-api} 79 | 80 | 81 | com.google.guava 82 | guava 83 | ${version.guava} 84 | 85 | 86 | javax.inject 87 | javax.inject 88 | ${version.javax.inject} 89 | 90 | 91 | junit 92 | junit 93 | ${version.junit} 94 | 95 | 96 | com.tngtech.java 97 | junit-dataprovider 98 | ${version.junit-dataprovider} 99 | test 100 | 101 | 102 | org.apache.maven 103 | maven-core 104 | ${version.maven} 105 | 106 | 107 | org.apache.maven 108 | maven-model 109 | ${version.maven} 110 | 111 | 112 | org.apache.maven.plugin-tools 113 | maven-plugin-annotations 114 | ${version.maven-plugin-plugin} 115 | 116 | 117 | org.apache.maven 118 | maven-plugin-api 119 | ${version.maven} 120 | 121 | 122 | org.apache.maven 123 | maven-settings 124 | ${version.maven} 125 | 126 | 127 | org.slf4j 128 | slf4j-simple 129 | ${version.slf4j-simple} 130 | 131 | 132 | org.jboss.weld.se 133 | weld-se 134 | ${version.weld-se} 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | org.apache.maven.plugins 143 | maven-compiler-plugin 144 | 145 | ${version.java} 146 | ${version.java} 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | disable-java8-doclint 156 | 157 | [1.8,) 158 | 159 | 160 | 161 | 162 | 163 | org.apache.maven.plugins 164 | maven-javadoc-plugin 165 | 166 | -Xdoclint:none 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/AbstractCDIMojo.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi; 2 | 3 | import java.io.File; 4 | import java.io.InputStream; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.util.Collection; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | import javax.enterprise.event.Observes; 13 | import javax.enterprise.inject.spi.AfterBeanDiscovery; 14 | import javax.enterprise.inject.spi.BeanManager; 15 | import javax.enterprise.inject.spi.Extension; 16 | import javax.enterprise.inject.spi.ProcessAnnotatedType; 17 | import javax.inject.Named; 18 | 19 | import org.apache.maven.execution.MavenSession; 20 | import org.apache.maven.model.Dependency; 21 | import org.apache.maven.plugin.AbstractMojo; 22 | import org.apache.maven.plugin.MojoExecution; 23 | import org.apache.maven.plugin.MojoExecutionException; 24 | import org.apache.maven.plugin.MojoFailureException; 25 | import org.apache.maven.plugin.PluginParameterExpressionEvaluator; 26 | import org.apache.maven.plugin.descriptor.MojoDescriptor; 27 | import org.apache.maven.plugin.descriptor.PluginDescriptor; 28 | import org.apache.maven.plugins.annotations.Component; 29 | import org.apache.maven.plugins.annotations.Parameter; 30 | import org.apache.maven.project.MavenProject; 31 | import org.apache.maven.settings.Settings; 32 | import org.eclipse.aether.RepositorySystemSession; 33 | import org.eclipse.aether.impl.ArtifactResolver; 34 | import org.eclipse.aether.repository.RemoteRepository; 35 | import org.jboss.weld.environment.se.Weld; 36 | import org.jboss.weld.environment.se.WeldContainer; 37 | 38 | import com.google.common.base.Optional; 39 | import com.google.common.base.Preconditions; 40 | import com.google.common.collect.Maps; 41 | import com.google.common.collect.Sets; 42 | import com.itemis.maven.plugins.cdi.annotations.MojoProduces; 43 | import com.itemis.maven.plugins.cdi.annotations.ProcessingStep; 44 | import com.itemis.maven.plugins.cdi.internal.beans.CdiBeanWrapper; 45 | import com.itemis.maven.plugins.cdi.internal.beans.CdiProducerBean; 46 | import com.itemis.maven.plugins.cdi.internal.util.CDIUtil; 47 | import com.itemis.maven.plugins.cdi.internal.util.MavenUtil; 48 | import com.itemis.maven.plugins.cdi.internal.util.workflow.ProcessingWorkflow; 49 | import com.itemis.maven.plugins.cdi.internal.util.workflow.WorkflowExecutor; 50 | import com.itemis.maven.plugins.cdi.internal.util.workflow.WorkflowUtil; 51 | import com.itemis.maven.plugins.cdi.internal.util.workflow.WorkflowValidator; 52 | import com.itemis.maven.plugins.cdi.logging.MavenLogWrapper; 53 | 54 | /** 55 | * An abstract Mojo that enables CDI-based dependency injection for the current maven plugin.
56 | * This Mojo enables you to decouple different parts of your plugin implementation and also dynamically inject 57 | * additional funktionality into your plugin.
58 | *
59 | * 60 | * ATTENTION: Please do not use annotations such as {@code @javax.inject.Inject} or 61 | * {@code @javax.enterprise.inject.Produces} directly in your Mojo! There are special replacements for that in the 62 | * annotations package of this library. Using CDI annotations directly in the Mojo would trigger Maven's own CDI 63 | * adaption!
64 | *
65 | * 66 | * Using this abstract Mojo as the parent of your own Mojo, you can simply see the Mojo class as a data container whose 67 | * single responsibility is to provide parameters for your business logic implementations. Simply get the 68 | * Mojo parameters injected and use the producer annotation to provide the bean to your implementations: 69 | * 70 | *
 71 |  * @Parameter
 72 |  * @MojoProduces
 73 |  * @Named("sourcePath")
 74 |  * private String sourcePath;
 75 |  *
 76 |  * @Parameter(defaultValue = "${reactorProjects}", readonly = true, required = true)
 77 |  * @MojoProduces
 78 |  * @Named("reactorProjects")
 79 |  * private List<MavenProject> reactorProjects;
 80 |  * 
81 | * 82 | * Or use a producer method for the logger: 83 | * 84 | *
 85 |  * @MojoProduces
 86 |  * public MavenLogWrapper createLogWrapper() {
 87 |  *   MavenLogWrapper log = new MavenLogWrapper(getLog());
 88 |  *   if (this.enableLogTimestamps) {
 89 |  *     log.enableLogTimestamps();
 90 |  *   }
 91 |  *   return log;
 92 |  * }
 93 |  * 
94 | * 95 | * ATTENTION: Make sure to not override the {@link #execute()} method since this method is responsible for the 96 | * CDI setup and will 97 | * trigger your business logic impelementations automatically.
98 | * Implement your business logic in one or more classes that are annotated with {@link ProcessingStep} and implement 99 | * {@link CDIMojoProcessingStep}. Then orchestrate your standard business workflow in a worflow descriptor file.
100 | *
101 | * 102 | *

The Workflow Descriptor

103 | * 112 | * 113 | *

A Sample Workflow

114 | * goal=perform 115 | * workflow-file=META-INF/workflows/perform 116 | * 117 | *
118 |  * init
119 |  * # The following steps can be run in parallel since they do not modify the project but only perform some checks
120 |  * parallel {
121 |  *   checkUser
122 |  *   checkConnection
123 |  *   checkAether
124 |  * }
125 |  * compute
126 |  * upload
127 |  * validate
128 |  * 
129 | * 130 | * @author Stanley Hillner 131 | * @since 1.0.0 132 | */ 133 | public class AbstractCDIMojo extends AbstractMojo implements Extension { 134 | private static final String SYSPROP_PRINT_WF = "printWorkflow"; 135 | private static final String SYSPROP_PRINT_STEPS = "printSteps"; 136 | 137 | @Component 138 | private ArtifactResolver _resolver; 139 | 140 | @Parameter(defaultValue = "${settings}", readonly = true, required = true) 141 | private Settings _settings; 142 | 143 | @Parameter(readonly = true, defaultValue = "${repositorySystemSession}") 144 | private RepositorySystemSession _repoSystemSession; 145 | 146 | @Parameter(readonly = true, defaultValue = "${project.remotePluginRepositories}") 147 | private List _pluginRepos; 148 | 149 | @Parameter(property = "mojoExecution", readonly = true) 150 | private MojoExecution _mojoExecution; 151 | 152 | @Parameter(property = "session", readonly = true) 153 | private MavenSession _session; 154 | 155 | @Parameter(property = "workflow") 156 | private File workflowDescriptor; 157 | 158 | @Parameter(defaultValue = "true", property = "enableLogTimestamps") 159 | @MojoProduces 160 | @Named("enableLogTimestamps") 161 | private boolean enableLogTimestamps; 162 | 163 | private ProcessingWorkflow workflow; 164 | 165 | private Map allAvailableProcessingSteps = Maps.newHashMap(); 166 | 167 | @MojoProduces 168 | public final MavenLogWrapper createLogWrapper() { 169 | MavenLogWrapper log = new MavenLogWrapper(getLog()); 170 | if (this.enableLogTimestamps) { 171 | log.enableLogTimestamps(); 172 | } 173 | return log; 174 | } 175 | 176 | @Override 177 | public final void execute() throws MojoExecutionException, MojoFailureException { 178 | if (System.getProperty(SYSPROP_PRINT_WF) != null) { 179 | WorkflowUtil.printWorkflow(getGoalName(), getPluginDescriptor(), Optional.fromNullable(this.workflowDescriptor), 180 | createLogWrapper()); 181 | return; 182 | } 183 | 184 | System.setProperty("org.jboss.logging.provider", "slf4j"); 185 | String logLevel = "info"; 186 | if (getLog().isDebugEnabled()) { 187 | logLevel = "debug"; 188 | } 189 | System.setProperty("org.slf4j.simpleLogger.log.org.jboss.weld", logLevel); 190 | 191 | Weld weld = new Weld(); 192 | weld.addExtension(this); 193 | addPluginDependencies(weld); 194 | WeldContainer weldContainer = null; 195 | try { 196 | weldContainer = weld.initialize(); 197 | if (System.getProperty(SYSPROP_PRINT_STEPS) != null) { 198 | WorkflowUtil.printAvailableSteps(this.allAvailableProcessingSteps, createLogWrapper()); 199 | return; 200 | } 201 | 202 | WorkflowUtil.addExecutionContexts(getWorkflow()); 203 | Map processingSteps = getAllProcessingSteps(weldContainer); 204 | 205 | PluginParameterExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(this._session, 206 | this._mojoExecution); 207 | WorkflowExecutor executor = new WorkflowExecutor(getWorkflow(), processingSteps, getLog(), expressionEvaluator); 208 | executor.validate(!this._settings.isOffline()); 209 | executor.execute(); 210 | } finally { 211 | if (weldContainer != null && weldContainer.isRunning()) { 212 | weldContainer.shutdown(); 213 | } 214 | } 215 | } 216 | 217 | private ProcessingWorkflow getWorkflow() throws MojoExecutionException, MojoFailureException { 218 | if (this.workflow == null) { 219 | InputStream wfDescriptor = WorkflowUtil.getWorkflowDescriptor(getGoalName(), getPluginDescriptor(), 220 | Optional.fromNullable(this.workflowDescriptor), createLogWrapper()); 221 | 222 | try { 223 | WorkflowValidator.validateSyntactically(wfDescriptor); 224 | } catch (RuntimeException e) { 225 | throw new MojoFailureException(e.getMessage()); 226 | } 227 | 228 | wfDescriptor = WorkflowUtil.getWorkflowDescriptor(getGoalName(), getPluginDescriptor(), 229 | Optional.fromNullable(this.workflowDescriptor), createLogWrapper()); 230 | this.workflow = WorkflowUtil.parseWorkflow(wfDescriptor, getGoalName()); 231 | } 232 | return this.workflow; 233 | } 234 | 235 | @SuppressWarnings("unused") 236 | // will be called automatically by the CDI container for all annotated types 237 | private void skipUnusedStepsFromBeanDiscovery(@Observes ProcessAnnotatedType event, BeanManager beanManager) 238 | throws MojoExecutionException, MojoFailureException { 239 | // https://github.com/shillner/maven-cdi-plugin-utils/issues/14 240 | Class type = event.getAnnotatedType().getJavaClass(); 241 | ProcessingStep annotation = type.getAnnotation(ProcessingStep.class); 242 | if (annotation != null) { 243 | // adding the step to the list of all available processing steps 244 | String id = annotation.id(); 245 | Preconditions.checkState(!this.allAvailableProcessingSteps.containsKey(id), 246 | "The processing step id '" + id + "' is not unique!"); 247 | this.allAvailableProcessingSteps.put(id, annotation); 248 | 249 | // vetoing the bean discovery of a step that is not part of the current workflow 250 | // this prevents the issue that data shall be injected that isn't produced anywhere! 251 | ProcessingWorkflow workflow = getWorkflow(); 252 | if (!workflow.containsStep(annotation.id())) { 253 | event.veto(); 254 | } 255 | } 256 | } 257 | 258 | @SuppressWarnings("unused") 259 | // will be called automatically by the CDI container once the bean discovery has finished 260 | private void processMojoCdiProducerFields(@Observes AfterBeanDiscovery event, BeanManager beanManager) 261 | throws MojoExecutionException { 262 | 263 | Class cls = getClass(); 264 | Set fields = Sets.newHashSet(); 265 | 266 | while (cls != AbstractCDIMojo.class) { 267 | fields.addAll(Sets.newHashSet(cls.getFields())); 268 | fields.addAll(Sets.newHashSet(cls.getDeclaredFields())); 269 | cls = cls.getSuperclass(); 270 | } 271 | 272 | for (Field f : fields) { 273 | if (f.isAnnotationPresent(MojoProduces.class)) { 274 | try { 275 | f.setAccessible(true); 276 | event.addBean( 277 | new CdiBeanWrapper(f.get(this), f.getGenericType(), f.getType(), CDIUtil.getCdiQualifiers(f))); 278 | } catch (Throwable t) { 279 | throw new MojoExecutionException("Could not process CDI producer field of the Mojo.", t); 280 | } 281 | } 282 | } 283 | } 284 | 285 | @SuppressWarnings({ "unused", "unchecked", "rawtypes" }) 286 | // will be called automatically by the CDI container once the bean discovery has finished 287 | private void processMojoCdiProducerMethods(@Observes AfterBeanDiscovery event, BeanManager beanManager) 288 | throws MojoExecutionException { 289 | // no method parameter injection possible at the moment since the container is not yet initialized at this point! 290 | Class cls = getClass(); 291 | Set methods = Sets.newHashSet(); 292 | 293 | while (cls != AbstractCDIMojo.class) { 294 | methods.addAll(Sets.newHashSet(cls.getMethods())); 295 | methods.addAll(Sets.newHashSet(cls.getDeclaredMethods())); 296 | cls = cls.getSuperclass(); 297 | } 298 | 299 | for (Method m : methods) { 300 | if (m.getReturnType() != Void.class && m.isAnnotationPresent(MojoProduces.class)) { 301 | try { 302 | event.addBean(new CdiProducerBean(m, this, beanManager, m.getGenericReturnType(), m.getReturnType(), 303 | CDIUtil.getCdiQualifiers(m))); 304 | } catch (Throwable t) { 305 | throw new MojoExecutionException("Could not process CDI producer method of the Mojo.", t); 306 | } 307 | } 308 | } 309 | } 310 | 311 | private void addPluginDependencies(Weld weld) throws MojoExecutionException { 312 | PluginDescriptor pluginDescriptor = getPluginDescriptor(); 313 | List dependencies = pluginDescriptor.getPlugin().getDependencies(); 314 | for (Dependency d : dependencies) { 315 | Optional f = MavenUtil.resolvePluginDependency(d, this._pluginRepos, this._resolver, 316 | this._repoSystemSession); 317 | if (f.isPresent()) { 318 | CDIUtil.addAllClasses(weld, getClass().getClassLoader(), f.get(), getLog()); 319 | } else { 320 | throw new MojoExecutionException("Could not resolve the following plugin dependency: " + d); 321 | } 322 | } 323 | } 324 | 325 | private Map getAllProcessingSteps(WeldContainer weldContainer) { 326 | Map steps = Maps.newHashMap(); 327 | Collection beans = CDIUtil.getAllBeansOfType(weldContainer, CDIMojoProcessingStep.class); 328 | for (CDIMojoProcessingStep bean : beans) { 329 | ProcessingStep annotation = bean.getClass().getAnnotation(ProcessingStep.class); 330 | if (annotation != null) { 331 | String id = annotation.id(); 332 | Preconditions.checkState(!steps.containsKey(id), "The processing step id '" + id + "' is not unique!"); 333 | steps.put(id, bean); 334 | } 335 | } 336 | return steps; 337 | } 338 | 339 | private String getGoalName() { 340 | PluginDescriptor pluginDescriptor = getPluginDescriptor(); 341 | for (MojoDescriptor mojoDescriptor : pluginDescriptor.getMojos()) { 342 | if (mojoDescriptor.getImplementation().equals(getClass().getName())) { 343 | return mojoDescriptor.getGoal(); 344 | } 345 | } 346 | return null; 347 | } 348 | 349 | private PluginDescriptor getPluginDescriptor() { 350 | return (PluginDescriptor) getPluginContext().get("pluginDescriptor"); 351 | } 352 | 353 | private MavenProject getProject() { 354 | return (MavenProject) getPluginContext().get("project"); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/CDIMojoProcessingStep.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi; 2 | 3 | import org.apache.maven.plugin.MojoExecutionException; 4 | import org.apache.maven.plugin.MojoFailureException; 5 | 6 | import com.itemis.maven.plugins.cdi.annotations.ProcessingStep; 7 | import com.itemis.maven.plugins.cdi.annotations.RollbackOnError; 8 | 9 | /** 10 | * Classes of this type will automatically be executed as the primary plugin code once the CDI container is set up and 11 | * the corresponding workflow for the goal is parsed.
12 | * You can influence the execution order of the processing steps using the workflow descriptor which references the ids 13 | * specified in the {@link ProcessingStep} annotation.
14 | *
15 | * 16 | * Example Mojo: 17 | * 18 | *
19 |  * @ProcessingStep(id = "test", description = "any description of this step")
20 |  * public class TestStep implements CDIMojoProcessingStep {
21 |  *   @Inject
22 |  *   @Named("sourcePath")
23 |  *   private String sourcePath;
24 |  *
25 |  *   public void execute() throws MojoExecutionException, MojoFailureException {
26 |  *     System.out.println(this.sourcePath);
27 |  *   }
28 |  * }
29 |  * 
30 | * 31 | * @author Stanley Hillner 32 | * @since 1.0.0 33 | */ 34 | public interface CDIMojoProcessingStep { 35 | 36 | /** 37 | * The primary execution method which will be called as soon as this Mojo is ready for execution.
38 | * If the execution of this method fails with any kind of {@link Throwable}, the Mojo will automatically search for 39 | * all methods annotated with the {@link RollbackOnError}. If the specified throwable type matches and the method's 40 | * optional argument matches the caught Exception type, the rollback method will be executed. It is possible to 41 | * declare multiple rollback methods for a Processing Step. 42 | * 43 | * @param context the context within which this step is executed. This is necessary for multiple parameterized 44 | * executions of the same step. 45 | * @throws MojoExecutionException if an unexpected execution exception occurred. 46 | * @throws MojoFailureException an expected exceptional case during the Mojo execution. 47 | */ 48 | void execute(ExecutionContext context) throws MojoExecutionException, MojoFailureException; 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/ExecutionContext.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Map.Entry; 7 | import java.util.Set; 8 | 9 | import org.apache.maven.plugin.PluginParameterExpressionEvaluator; 10 | import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; 11 | 12 | import com.google.common.collect.Iterables; 13 | import com.google.common.collect.Lists; 14 | import com.google.common.collect.Maps; 15 | 16 | public class ExecutionContext { 17 | public static final String PROJ_VAR_VERSION = "@{project.version}"; 18 | public static final String PROJ_VAR_GID = "@{project.groupId}"; 19 | public static final String PROJ_VAR_AID = "@{project.artifactId}"; 20 | 21 | private String stepId; 22 | private String stepQualifier; 23 | private Map mappedData; 24 | private Iterable unmappedData; 25 | private boolean variablesExpanded; 26 | private Map mappedRollbackData; 27 | private Iterable unmappedRollbackData; 28 | 29 | private ExecutionContext(String stepId, String qualifier, Map mappedData, 30 | Iterable unmappedData, Map mappedRollbackData, Iterable unmappedRollbackData) { 31 | this.stepId = stepId; 32 | this.stepQualifier = qualifier; 33 | this.mappedData = mappedData; 34 | this.unmappedData = unmappedData; 35 | this.mappedRollbackData = mappedRollbackData; 36 | this.unmappedRollbackData = unmappedRollbackData; 37 | } 38 | 39 | public static Builder builder(String stepId) { 40 | return new Builder(stepId); 41 | } 42 | 43 | public String getStepId() { 44 | return this.stepId; 45 | } 46 | 47 | public String getQualifier() { 48 | return this.stepQualifier; 49 | } 50 | 51 | public String getCompositeStepId() { 52 | return this.stepId + (this.stepQualifier != null ? "[" + this.stepQualifier + "]" : ""); 53 | } 54 | 55 | public boolean hasMappedData() { 56 | return !this.mappedData.isEmpty(); 57 | } 58 | 59 | public boolean hasUnmappedData() { 60 | return !Iterables.isEmpty(this.unmappedData); 61 | } 62 | 63 | public boolean hasMappedRollbackData() { 64 | return !this.mappedRollbackData.isEmpty(); 65 | } 66 | 67 | public boolean hasUnmappedRollbackData() { 68 | return !Iterables.isEmpty(this.unmappedRollbackData); 69 | } 70 | 71 | public Set getMappedDataKeys() { 72 | return this.mappedData.keySet(); 73 | } 74 | 75 | public String getMappedDate(String key) { 76 | return this.mappedData.get(key); 77 | } 78 | 79 | public boolean containsMappedDate(String key) { 80 | return this.mappedData.containsKey(key); 81 | } 82 | 83 | public Iterable getUnmappedData() { 84 | return this.unmappedData; 85 | } 86 | 87 | public Set getMappedRollbackDataKeys() { 88 | return this.mappedRollbackData.keySet(); 89 | } 90 | 91 | public String getMappedRollbackDate(String key) { 92 | return this.mappedRollbackData.get(key); 93 | } 94 | 95 | public boolean containsMappedRollbackDate(String key) { 96 | return this.mappedRollbackData.containsKey(key); 97 | } 98 | 99 | public Iterable getUnmappedRollbackData() { 100 | return this.unmappedRollbackData; 101 | } 102 | 103 | public void expandProjectVariables(PluginParameterExpressionEvaluator expressionEvaluator) { 104 | if (this.variablesExpanded) { 105 | return; 106 | } 107 | expandUnmappedData(expressionEvaluator); 108 | expandMappedData(expressionEvaluator); 109 | this.variablesExpanded = true; 110 | } 111 | 112 | private void expandUnmappedData(PluginParameterExpressionEvaluator expressionEvaluator) { 113 | if (hasUnmappedData()) { 114 | List newData = Lists.newArrayList(); 115 | for (String date : this.unmappedData) { 116 | newData.add(expand(date, expressionEvaluator)); 117 | } 118 | this.unmappedData = Iterables.unmodifiableIterable(newData); 119 | } 120 | 121 | if (hasUnmappedRollbackData()) { 122 | List newData = Lists.newArrayList(); 123 | for (String date : this.unmappedRollbackData) { 124 | newData.add(expand(date, expressionEvaluator)); 125 | } 126 | this.unmappedRollbackData = Iterables.unmodifiableIterable(newData); 127 | } 128 | } 129 | 130 | private void expandMappedData(PluginParameterExpressionEvaluator expressionEvaluator) { 131 | if (hasMappedData()) { 132 | Map newData = Maps.newHashMap(); 133 | for (Entry entry : this.mappedData.entrySet()) { 134 | newData.put(entry.getKey(), expand(entry.getValue(), expressionEvaluator)); 135 | } 136 | this.mappedData = Collections.unmodifiableMap(this.mappedData); 137 | } 138 | if (hasMappedRollbackData()) { 139 | Map newData = Maps.newHashMap(); 140 | for (Entry entry : this.mappedRollbackData.entrySet()) { 141 | newData.put(entry.getKey(), expand(entry.getValue(), expressionEvaluator)); 142 | } 143 | this.mappedRollbackData = Collections.unmodifiableMap(this.mappedRollbackData); 144 | } 145 | } 146 | 147 | private String expand(String s, PluginParameterExpressionEvaluator expressionEvaluator) { 148 | String var = s.replace("@{", "${"); 149 | try { 150 | String evaluated = expressionEvaluator.evaluate(var).toString(); 151 | return evaluated; 152 | } catch (ExpressionEvaluationException e) { 153 | throw new RuntimeException(e.getMessage(), e); 154 | } 155 | } 156 | 157 | public static class Builder { 158 | private String id; 159 | private String qualifier; 160 | private List unmappedData; 161 | private Map mappedData; 162 | private List unmappedRollbackData; 163 | private Map mappedRollbackData; 164 | 165 | public Builder(String stepId) { 166 | this.id = stepId; 167 | this.mappedData = Maps.newHashMap(); 168 | this.unmappedData = Lists.newArrayList(); 169 | this.mappedRollbackData = Maps.newHashMap(); 170 | this.unmappedRollbackData = Lists.newArrayList(); 171 | } 172 | 173 | public Builder setQualifier(String qualifier) { 174 | this.qualifier = qualifier; 175 | return this; 176 | } 177 | 178 | public Builder addData(String... data) { 179 | for (String date : data) { 180 | this.unmappedData.add(date); 181 | } 182 | return this; 183 | } 184 | 185 | public Builder addRollbackData(String... data) { 186 | for (String date : data) { 187 | this.unmappedRollbackData.add(date); 188 | } 189 | return this; 190 | } 191 | 192 | public Builder addData(String key, String value) { 193 | this.mappedData.put(key, value); 194 | return this; 195 | } 196 | 197 | public Builder addRollbackData(String key, String value) { 198 | this.mappedRollbackData.put(key, value); 199 | return this; 200 | } 201 | 202 | public ExecutionContext build() { 203 | return new ExecutionContext(this.id, this.qualifier, Collections.unmodifiableMap(this.mappedData), 204 | Iterables.unmodifiableIterable(this.unmappedData), Collections.unmodifiableMap(this.mappedRollbackData), 205 | Iterables.unmodifiableIterable(this.unmappedRollbackData)); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/annotations/MojoInject.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.annotations; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * The CDI injection annotation for use within the Mojo. Currently only usable for parameter injection in producer 11 | * methods
12 | * Note that it is not possible to use {@code @javax.inject.Inject} directly in your Mojo since this would 13 | * trigger Maven's own pseudo CDI implementation. Within all other beans that are managed by CDI you can use 14 | * {@code @Inject} as usual. 15 | * 16 | * @author Stanley Hillner 17 | * @since 1.0.0 18 | */ 19 | @Retention(RUNTIME) 20 | @Target({ METHOD }) 21 | public @interface MojoInject { 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/annotations/MojoProduces.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.annotations; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.ElementType.METHOD; 5 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 6 | 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * A special CDI producer annotation that must be used if you want to declare fields or methods of the Mojo as 12 | * producers.
13 | * Note that it is not possible to use {@code @javax.enterprise.inject.Produces} as the producer annotation in 14 | * your Mojo since this would activate Maven's own pseudo CDI implementation. All other beans that are created by CDI 15 | * are then able to use {@code @Produces} as usual. 16 | * 17 | * @author Stanley Hillner 18 | * @since 1.0.0 19 | */ 20 | @Retention(RUNTIME) 21 | @Target({ METHOD, FIELD }) 22 | public @interface MojoProduces { 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/annotations/ProcessingStep.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.annotations; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import com.itemis.maven.plugins.cdi.CDIMojoProcessingStep; 10 | 11 | /** 12 | * A class-level annotation used to specify some metadata for the executions of the {@link CDIMojoProcessingStep 13 | * injected CDI Mojo instances}.
14 | * 15 | * @author Stanley Hillner 16 | * @since 1.0.0 17 | */ 18 | @Target({ TYPE }) 19 | @Retention(RUNTIME) 20 | public @interface ProcessingStep { 21 | /** 22 | * @return the id of the processing step which is used for the orchestration of the processing workflow. 23 | * @since 2.0.0 24 | */ 25 | String id(); 26 | 27 | /** 28 | * @return a description of this processing step's responsibilities. This description will later be used as output to 29 | * users of your plugin that want to provide a custom processing workflow. 30 | * @since 2.0.0 31 | */ 32 | String description() default ""; 33 | 34 | /** 35 | * If this step requires a network connection but Maven is executed in offline mode, the workflow won't ever be 36 | * executed and the build will fail fast with an appropriate error message. 37 | * 38 | * @return {@code true} if the execution of this processing step requires Maven to operate in online mode. Return 39 | * {@code false} if the step can be executed safely without having a network connection. 40 | * @since 2.1.0 41 | */ 42 | boolean requiresOnline() default true; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/annotations/RollbackOnError.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.annotations; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import com.itemis.maven.plugins.cdi.CDIMojoProcessingStep; 10 | import com.itemis.maven.plugins.cdi.ExecutionContext; 11 | 12 | /** 13 | * This annotation can be put on any method of an {@link CDIMojoProcessingStep} with the following purpose and 14 | * restrictions: 15 | *
16 | *
17 | * Purpose: As soon as the execution of the Mojo instance has failed throwing an exception, the rollback 18 | * method(s) 19 | * are called automatically.
20 | *
21 | * Restrictions: 22 | *
    23 | *
  • Any return type of the method will be ignored, method should be void!
  • 24 | *
  • Method may have one or two parameters: 25 | *
      26 | *
    • Max. one of type {@code } which takes the cause of the rollback.
    • 27 | *
    • Max. one of type {@link ExecutionContext} to get the context of the original step execution.
    • 28 | *
    29 | *
  • 30 | *
  • Otherwise the signature must not declare any arguments
  • 31 | *
  • If the method signature declares a Throwable as one of its arguments but the type does not match the caught 32 | * exception, the method is skipped. If there are other rollback methods declared with matching exception types or 33 | * without one, these will be executed as usual.
  • 34 | *
35 | * 36 | * It is possible to declare several rollback methods! Each method with matching exception types will be executed in 37 | * ascending alphabetical order. 38 | * 39 | * @author Stanley Hillner 40 | * @since 1.0.0 41 | */ 42 | @Target({ METHOD }) 43 | @Retention(RUNTIME) 44 | public @interface RollbackOnError { 45 | /** 46 | * @return the error types for which this rollback method is triggered. 47 | */ 48 | Class[] value() default {}; 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/beans/CdiBeanWrapper.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.beans; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Type; 5 | import java.util.Collections; 6 | import java.util.Set; 7 | 8 | import javax.enterprise.context.Dependent; 9 | import javax.enterprise.context.spi.CreationalContext; 10 | import javax.enterprise.inject.spi.Bean; 11 | import javax.enterprise.inject.spi.InjectionPoint; 12 | 13 | public class CdiBeanWrapper implements Bean { 14 | 15 | private T instance; 16 | private Set qualifiers; 17 | private Type type; 18 | private Class instanceClass; 19 | 20 | public CdiBeanWrapper(T instance, Type type, Class instanceClass, Set qualifiers) { 21 | this.instance = instance; 22 | this.type = type; 23 | this.instanceClass = instanceClass; 24 | this.qualifiers = qualifiers; 25 | } 26 | 27 | @Override 28 | public T create(CreationalContext creationalContext) { 29 | return this.instance; 30 | } 31 | 32 | @Override 33 | public void destroy(T instance, CreationalContext creationalContext) { 34 | } 35 | 36 | @Override 37 | public Set getTypes() { 38 | return Collections.singleton(this.type); 39 | } 40 | 41 | @Override 42 | public Set getQualifiers() { 43 | return this.qualifiers; 44 | } 45 | 46 | @Override 47 | public Class getScope() { 48 | return Dependent.class; 49 | } 50 | 51 | @Override 52 | public String getName() { 53 | return null; 54 | } 55 | 56 | @Override 57 | public Set> getStereotypes() { 58 | return Collections.emptySet(); 59 | } 60 | 61 | @Override 62 | public boolean isAlternative() { 63 | return false; 64 | } 65 | 66 | @Override 67 | public Class getBeanClass() { 68 | return this.instanceClass; 69 | } 70 | 71 | @Override 72 | public Set getInjectionPoints() { 73 | return Collections.emptySet(); 74 | } 75 | 76 | @Override 77 | public boolean isNullable() { 78 | return true; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/beans/CdiProducerBean.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.beans; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.ParameterizedType; 6 | import java.lang.reflect.Type; 7 | import java.util.Collections; 8 | import java.util.Set; 9 | 10 | import javax.enterprise.context.Dependent; 11 | import javax.enterprise.context.spi.CreationalContext; 12 | import javax.enterprise.inject.Typed; 13 | import javax.enterprise.inject.spi.Bean; 14 | import javax.enterprise.inject.spi.BeanManager; 15 | import javax.enterprise.inject.spi.InjectionPoint; 16 | import javax.inject.Qualifier; 17 | 18 | import org.jboss.weld.literal.DefaultLiteral; 19 | 20 | import com.google.common.collect.Iterables; 21 | import com.google.common.collect.Sets; 22 | 23 | public class CdiProducerBean implements Bean { 24 | private Method method; 25 | private Object hostInstance; 26 | private BeanManager beanManager; 27 | private Set qualifiers; 28 | private Set types; 29 | private Class instanceClass; 30 | 31 | public CdiProducerBean(Method method, Object hostInstance, BeanManager beanManager, Type type, Class instanceClass, 32 | Set qualifiers) { 33 | this.method = method; 34 | this.hostInstance = hostInstance; 35 | this.beanManager = beanManager; 36 | this.instanceClass = instanceClass; 37 | this.qualifiers = qualifiers; 38 | this.types = calcBeanTypes(type); 39 | } 40 | 41 | private Set calcBeanTypes(Type implTpye) { 42 | Set beanTypes = Sets.newHashSet(); 43 | 44 | if (implTpye instanceof ParameterizedType) { 45 | beanTypes.add((ParameterizedType) implTpye); 46 | } else { 47 | Typed typedAnnotation = ((Class) implTpye).getAnnotation(Typed.class); 48 | if (typedAnnotation != null) { 49 | for (Class cls : typedAnnotation.value()) { 50 | beanTypes.add(cls); 51 | } 52 | } else { 53 | beanTypes.addAll(getTypeClasses((Class) implTpye)); 54 | } 55 | } 56 | return beanTypes; 57 | } 58 | 59 | private Set> getTypeClasses(Class cls) { 60 | if (cls == null) { 61 | return Collections.emptySet(); 62 | } 63 | 64 | Set> classes = Sets.newHashSet(); 65 | classes.add(cls); 66 | classes.addAll(getTypeClasses(cls.getSuperclass())); 67 | for (Class iface : cls.getInterfaces()) { 68 | classes.addAll(getTypeClasses(iface)); 69 | } 70 | return classes; 71 | } 72 | 73 | @SuppressWarnings("unchecked") 74 | @Override 75 | public T create(CreationalContext creationalContext) { 76 | Object[] params = new Object[0]; 77 | Class[] parameterTypes = this.method.getParameterTypes(); 78 | Annotation[][] parameterAnnotations = this.method.getParameterAnnotations(); 79 | params = new Object[parameterTypes.length]; 80 | for (int i = 0; i < parameterTypes.length; i++) { 81 | Set qualifiers = getCdiQualifiers(parameterAnnotations[i]); 82 | 83 | Class paramType = parameterTypes[i]; 84 | Set> beans = this.beanManager.getBeans(paramType, qualifiers.toArray(new Annotation[qualifiers.size()])); 85 | if (beans.size() == 1) { 86 | Bean bean = Iterables.get(beans, 0); 87 | Object reference = this.beanManager.getReference(bean, paramType, 88 | this.beanManager.createCreationalContext(bean)); 89 | params[i] = reference; 90 | } else { 91 | // FIXME handle -> ambiguous results 92 | } 93 | } 94 | 95 | T instance = null; 96 | try { 97 | this.method.setAccessible(true); 98 | instance = (T) this.method.invoke(this.hostInstance, params); 99 | } catch (Throwable t) { 100 | t.printStackTrace(); 101 | } 102 | return instance; 103 | } 104 | 105 | @Override 106 | public void destroy(T instance, CreationalContext creationalContext) { 107 | } 108 | 109 | @Override 110 | public Set getTypes() { 111 | return this.types; 112 | } 113 | 114 | @Override 115 | public Set getQualifiers() { 116 | return this.qualifiers; 117 | } 118 | 119 | @Override 120 | public Class getScope() { 121 | return Dependent.class; 122 | } 123 | 124 | @Override 125 | public String getName() { 126 | return null; 127 | } 128 | 129 | @Override 130 | public Set> getStereotypes() { 131 | return Collections.emptySet(); 132 | } 133 | 134 | @Override 135 | public boolean isAlternative() { 136 | return false; 137 | } 138 | 139 | @Override 140 | public Class getBeanClass() { 141 | return this.instanceClass; 142 | } 143 | 144 | @Override 145 | public Set getInjectionPoints() { 146 | return Collections.emptySet(); 147 | } 148 | 149 | @Override 150 | public boolean isNullable() { 151 | return true; 152 | } 153 | 154 | private Set getCdiQualifiers(Annotation[] annotattions) { 155 | Set qualifiers = Sets.newHashSet(); 156 | for (Annotation annotation : annotattions) { 157 | if (annotation.annotationType().isAnnotationPresent(Qualifier.class)) { 158 | qualifiers.add(annotation); 159 | } 160 | } 161 | if (qualifiers.isEmpty()) { 162 | qualifiers.add(DefaultLiteral.INSTANCE); 163 | } 164 | return qualifiers; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/util/CDIUtil.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.lang.annotation.Annotation; 6 | import java.lang.reflect.AccessibleObject; 7 | import java.util.Collection; 8 | import java.util.Enumeration; 9 | import java.util.Set; 10 | import java.util.jar.JarEntry; 11 | import java.util.jar.JarFile; 12 | 13 | import javax.enterprise.context.spi.CreationalContext; 14 | import javax.enterprise.inject.spi.Bean; 15 | import javax.inject.Qualifier; 16 | 17 | import org.apache.maven.plugin.MojoExecutionException; 18 | import org.apache.maven.plugin.logging.Log; 19 | import org.jboss.weld.environment.se.Weld; 20 | import org.jboss.weld.environment.se.WeldContainer; 21 | import org.jboss.weld.literal.AnyLiteral; 22 | import org.jboss.weld.literal.DefaultLiteral; 23 | 24 | import com.google.common.base.Objects; 25 | import com.google.common.collect.Lists; 26 | import com.google.common.collect.Sets; 27 | import com.google.common.io.Files; 28 | 29 | /** 30 | * A utility class for handling CDI-specific tasks such as getting all beans of a specific type or adding beans to the 31 | * bean manager, ... 32 | * 33 | * @author Stanley Hillner 34 | * @since 2.0.0 35 | */ 36 | public class CDIUtil { 37 | private static final String FILE_EXTENSION_CLASS = "class"; 38 | 39 | /** 40 | * @param x the object from which all qualifier annotations shall be searched out. 41 | * @return a set of all qualifiers the object's class is annotated with. 42 | */ 43 | public static Set getCdiQualifiers(AccessibleObject x) { 44 | Set qualifiers = Sets.newHashSet(); 45 | for (Annotation annotation : x.getAnnotations()) { 46 | if (annotation.annotationType().isAnnotationPresent(Qualifier.class)) { 47 | qualifiers.add(annotation); 48 | } 49 | } 50 | if (qualifiers.isEmpty()) { 51 | qualifiers.add(DefaultLiteral.INSTANCE); 52 | } 53 | return qualifiers; 54 | } 55 | 56 | /** 57 | * Searches the container for all beans of a certain type without respecting qualifiers. 58 | * 59 | * @param weldContainer the container providing the beans. 60 | * @param type the type of the beans to search for. 61 | * @return a collection of all found beans of the specified type. 62 | */ 63 | public static Collection getAllBeansOfType(WeldContainer weldContainer, Class type) { 64 | Collection beans = Lists.newArrayList(); 65 | Set> cdiBeans = weldContainer.getBeanManager().getBeans(type, AnyLiteral.INSTANCE); 66 | // searches all beans for beans that have the matching goal name, ... 67 | for (Bean b : cdiBeans) { 68 | @SuppressWarnings("unchecked") 69 | Bean b2 = (Bean) b; 70 | CreationalContext creationalContext = weldContainer.getBeanManager().createCreationalContext(b2); 71 | T bean = b2.create(creationalContext); 72 | beans.add(bean); 73 | } 74 | return beans; 75 | } 76 | 77 | /** 78 | * Queries the specified file container (folder or JAR file) for all class files and adds all found classes to the 79 | * weld container so that these classes are later injectable. 80 | * 81 | * @param weld the CDI container to add the classes to. 82 | * @param classLoader the class loader used to query and load classes from the file container. 83 | * @param container the file container where to search classes. The container can be a folder or a JAR file. 84 | * @param log the log for processing output. 85 | * @throws MojoExecutionException if it was not possible to query the file container. 86 | */ 87 | public static void addAllClasses(Weld weld, ClassLoader classLoader, File container, Log log) 88 | throws MojoExecutionException { 89 | Set classNames = null; 90 | if (container.isFile() && container.getAbsolutePath().endsWith(".jar")) { 91 | try { 92 | JarFile jarFile = new JarFile(container); 93 | classNames = getAllClassNames(jarFile); 94 | } catch (IOException e) { 95 | throw new MojoExecutionException("Could not load the following JAR file: " + container.getAbsolutePath(), e); 96 | } 97 | } else if (container.isDirectory()) { 98 | classNames = getAllClassNames(container); 99 | } 100 | 101 | for (String className : classNames) { 102 | try { 103 | Class cls = classLoader.loadClass(className); 104 | weld.addBeanClass(cls); 105 | } catch (ClassNotFoundException e) { 106 | log.error("Could not load the following class which might cause later issues: " + className); 107 | if (log.isDebugEnabled()) { 108 | log.debug(e); 109 | } 110 | } 111 | } 112 | } 113 | 114 | private static Set getAllClassNames(JarFile f) { 115 | Set classNames = Sets.newHashSet(); 116 | Enumeration e = f.entries(); 117 | while (e.hasMoreElements()) { 118 | JarEntry je = (JarEntry) e.nextElement(); 119 | String extension = Files.getFileExtension(je.getName()); 120 | if (Objects.equal(FILE_EXTENSION_CLASS, extension)) { 121 | String className = je.getName().substring(0, je.getName().length() - 6); 122 | className = className.replace('/', '.'); 123 | classNames.add(className); 124 | } 125 | } 126 | return classNames; 127 | } 128 | 129 | private static Set getAllClassNames(File folder) { 130 | Set classNames = Sets.newHashSet(); 131 | for (File f : Files.fileTreeTraverser().preOrderTraversal(folder)) { 132 | String extension = Files.getFileExtension(f.getName()); 133 | if (Objects.equal(FILE_EXTENSION_CLASS, extension)) { 134 | String basePath = f.getAbsolutePath().replace(folder.getAbsolutePath(), ""); 135 | String className = basePath.substring(0, basePath.length() - 6); 136 | className = className.replace('/', '.').replace('\\', '.'); 137 | if (className.startsWith(".")) { 138 | className = className.substring(1); 139 | } 140 | classNames.add(className); 141 | } 142 | } 143 | return classNames; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/util/MavenUtil.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.util; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | 6 | import org.apache.maven.model.Dependency; 7 | import org.eclipse.aether.RepositorySystemSession; 8 | import org.eclipse.aether.artifact.Artifact; 9 | import org.eclipse.aether.artifact.DefaultArtifact; 10 | import org.eclipse.aether.impl.ArtifactResolver; 11 | import org.eclipse.aether.repository.RemoteRepository; 12 | import org.eclipse.aether.resolution.ArtifactRequest; 13 | import org.eclipse.aether.resolution.ArtifactResolutionException; 14 | import org.eclipse.aether.resolution.ArtifactResult; 15 | 16 | import com.google.common.base.Optional; 17 | 18 | /** 19 | * A utility class for maven related stuff such as resolving of dependencies, ... 20 | * 21 | * @author Stanley Hillner 22 | * @since 2.0.0 23 | */ 24 | public class MavenUtil { 25 | /** 26 | * Uses the aether to resolve a plugin dependency and returns the file for further processing. 27 | * 28 | * @param d the dependency to resolve. 29 | * @param pluginRepos the plugin repositories to use for dependency resolution. 30 | * @param resolver the resolver for aether access. 31 | * @param repoSystemSession the session for the resolver. 32 | * @return optionally a file which is the resolved dependency. 33 | */ 34 | public static Optional resolvePluginDependency(Dependency d, List pluginRepos, 35 | ArtifactResolver resolver, RepositorySystemSession repoSystemSession) { 36 | Artifact a = new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getClassifier(), d.getType(), d.getVersion()); 37 | ArtifactRequest artifactRequest = new ArtifactRequest(); 38 | artifactRequest.setArtifact(a); 39 | artifactRequest.setRepositories(pluginRepos); 40 | try { 41 | ArtifactResult artifactResult = resolver.resolveArtifact(repoSystemSession, artifactRequest); 42 | if (artifactResult.getArtifact() != null) { 43 | return Optional.fromNullable(artifactResult.getArtifact().getFile()); 44 | } 45 | return Optional.absent(); 46 | } catch (ArtifactResolutionException e) { 47 | return Optional.absent(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/util/workflow/ParallelWorkflowStep.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.util.workflow; 2 | 3 | import java.util.Collections; 4 | import java.util.Set; 5 | 6 | import com.google.common.base.MoreObjects; 7 | import com.google.common.base.MoreObjects.ToStringHelper; 8 | import com.google.common.base.Objects; 9 | import com.google.common.collect.Sets; 10 | import com.google.common.collect.Sets.SetView; 11 | 12 | /** 13 | * A representation of a parallel processing step of the workflow. 14 | * 15 | * @author Stanley Hillner 16 | * @since 2.1.0 17 | */ 18 | public class ParallelWorkflowStep implements WorkflowStep { 19 | private Set steps; 20 | 21 | private ParallelWorkflowStep() { 22 | this.steps = Sets.newHashSet(); 23 | } 24 | 25 | @Override 26 | public boolean isParallel() { 27 | return true; 28 | } 29 | 30 | public Set getSteps() { 31 | return Collections.unmodifiableSet(this.steps); 32 | } 33 | 34 | public static Builder builder() { 35 | return new Builder(); 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | ToStringHelper toStringHelper = MoreObjects.toStringHelper(this); 41 | toStringHelper.add("#steps", this.steps.size()); 42 | int i = 1; 43 | for (SimpleWorkflowStep step : this.steps) { 44 | toStringHelper.add("step " + i++, 45 | step.getStepId() + (step.getQualifier().isPresent() ? "[" + step.getQualifier().get() + "]" : "")); 46 | } 47 | return toStringHelper.toString(); 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return Objects.hashCode(this.steps.toArray()); 53 | } 54 | 55 | @Override 56 | public boolean equals(Object other) { 57 | if (other instanceof ParallelWorkflowStep) { 58 | ParallelWorkflowStep otherStep = (ParallelWorkflowStep) other; 59 | SetView intersection = Sets.intersection(this.steps, otherStep.steps); 60 | return intersection.size() == this.steps.size(); 61 | } 62 | return false; 63 | } 64 | 65 | public static class Builder { 66 | private ParallelWorkflowStep parallelStep; 67 | 68 | private Builder() { 69 | this.parallelStep = new ParallelWorkflowStep(); 70 | } 71 | 72 | public Builder addSteps(SimpleWorkflowStep... steps) { 73 | for (SimpleWorkflowStep step : steps) { 74 | this.parallelStep.steps.add(step); 75 | } 76 | return this; 77 | } 78 | 79 | public ParallelWorkflowStep build() { 80 | return this.parallelStep; 81 | } 82 | } 83 | 84 | @Override 85 | public boolean containsId(String id) { 86 | for (SimpleWorkflowStep step : this.steps) { 87 | if (step.containsId(id)) { 88 | return true; 89 | } 90 | } 91 | return false; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/util/workflow/ProcessingWorkflow.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.util.workflow; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import com.google.common.base.Objects; 8 | import com.google.common.collect.Lists; 9 | import com.google.common.collect.Maps; 10 | import com.itemis.maven.plugins.cdi.ExecutionContext; 11 | 12 | /** 13 | * A workflow representing the processing step order for a specific goal. 14 | * 15 | * @author Stanley Hillner 16 | * @since 2.0.0 17 | */ 18 | public class ProcessingWorkflow { 19 | private String goal; 20 | private List steps; 21 | private List finallySteps; 22 | private Map executionContexts; 23 | 24 | public ProcessingWorkflow(String goal) { 25 | this.goal = goal; 26 | this.steps = Lists.newArrayList(); 27 | this.finallySteps = Lists.newArrayList(); 28 | this.executionContexts = Maps.newHashMap(); 29 | } 30 | 31 | public String getGoal() { 32 | return this.goal; 33 | } 34 | 35 | public void addProcessingStep(WorkflowStep step) { 36 | this.steps.add(step); 37 | } 38 | 39 | public void addFinallyStep(SimpleWorkflowStep step) { 40 | this.finallySteps.add(step); 41 | } 42 | 43 | public void addExecutionContext(String stepId, ExecutionContext context) { 44 | this.executionContexts.put(stepId, context); 45 | } 46 | 47 | public List getProcessingSteps() { 48 | return Collections.unmodifiableList(this.steps); 49 | } 50 | 51 | public List getFinallySteps() { 52 | return Collections.unmodifiableList(this.finallySteps); 53 | } 54 | 55 | public ExecutionContext getExecutionContext(String stepId) { 56 | return this.executionContexts.get(stepId); 57 | } 58 | 59 | public boolean containsStep(String id) { 60 | for (WorkflowStep step : this.steps) { 61 | if (step.containsId(id)) { 62 | return true; 63 | } 64 | } 65 | for (SimpleWorkflowStep step : this.finallySteps) { 66 | if (Objects.equal(id, step.getStepId())) { 67 | return true; 68 | } 69 | } 70 | return false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/util/workflow/SimpleWorkflowStep.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.util.workflow; 2 | 3 | import com.google.common.base.MoreObjects; 4 | import com.google.common.base.MoreObjects.ToStringHelper; 5 | import com.google.common.base.Objects; 6 | import com.google.common.base.Optional; 7 | 8 | /** 9 | * A representation of a sequential processing step of the workflow. 10 | * 11 | * @author Stanley Hillner 12 | * @since 2.1.0 13 | */ 14 | public class SimpleWorkflowStep implements WorkflowStep { 15 | private String id; 16 | private Optional qualifier; 17 | private Optional defaultExecutionData; 18 | private Optional defaultRollbackData; 19 | 20 | public SimpleWorkflowStep(String id, Optional qualifier) { 21 | this.id = id; 22 | this.qualifier = qualifier; 23 | this.defaultExecutionData = Optional.absent(); 24 | this.defaultRollbackData = Optional.absent(); 25 | } 26 | 27 | @Override 28 | public boolean isParallel() { 29 | return false; 30 | } 31 | 32 | public String getStepId() { 33 | return this.id; 34 | } 35 | 36 | public Optional getQualifier() { 37 | return this.qualifier; 38 | } 39 | 40 | public String getCompositeStepId() { 41 | return this.id + (this.qualifier.isPresent() ? "[" + this.qualifier.get() + "]" : ""); 42 | } 43 | 44 | public void setDefaultExecutionData(String data) { 45 | this.defaultExecutionData = Optional.fromNullable(data); 46 | } 47 | 48 | public Optional getDefaultExecutionData() { 49 | return this.defaultExecutionData; 50 | } 51 | 52 | public void setDefaultRollbackData(String data) { 53 | this.defaultRollbackData = Optional.fromNullable(data); 54 | } 55 | 56 | public Optional getDefaultRollbackData() { 57 | return this.defaultRollbackData; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | ToStringHelper toStringHelper = MoreObjects.toStringHelper(this); 63 | toStringHelper.add("id", this.id); 64 | toStringHelper.add("qualifier", this.qualifier.or("---")); 65 | toStringHelper.add("defaultExecutionData", this.defaultExecutionData.or("---")); 66 | toStringHelper.add("defaultRollbackData", this.defaultRollbackData.or("---")); 67 | return toStringHelper.toString(); 68 | } 69 | 70 | @Override 71 | public int hashCode() { 72 | return Objects.hashCode(this.id, this.qualifier.orNull()); 73 | } 74 | 75 | @Override 76 | public boolean equals(Object other) { 77 | if (other instanceof SimpleWorkflowStep) { 78 | SimpleWorkflowStep otherStep = (SimpleWorkflowStep) other; 79 | return Objects.equal(this.id, otherStep.getStepId()) && Objects.equal(this.qualifier, otherStep.getQualifier()); 80 | } 81 | return false; 82 | } 83 | 84 | @Override 85 | public boolean containsId(String id) { 86 | return Objects.equal(id, this.id); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/util/workflow/WorkflowConstants.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.util.workflow; 2 | 3 | public class WorkflowConstants { 4 | public static final String KW_COMMENT = "#"; 5 | public static final String KW_PARALLEL = "parallel"; 6 | public static final String KW_BLOCK_OPEN = "{"; 7 | public static final String KW_BLOCK_CLOSE = "}"; 8 | public static final String KW_QUALIFIER_OPEN = "["; 9 | public static final String KW_QUALIFIER_CLOSE = "]"; 10 | public static final String KW_DATA_ASSIGNMENT = "="; 11 | public static final String KW_DATA = "data"; 12 | public static final String KW_ROLLBACK_DATA = "rollbackData"; 13 | public static final String KW_TRY = "try"; 14 | public static final String KW_FINALLY = "finally"; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/util/workflow/WorkflowExecutor.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.util.workflow; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Queue; 11 | import java.util.Set; 12 | import java.util.Stack; 13 | import java.util.concurrent.ExecutionException; 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.Executors; 16 | import java.util.concurrent.Future; 17 | 18 | import org.apache.commons.lang3.tuple.Pair; 19 | import org.apache.maven.plugin.MojoExecutionException; 20 | import org.apache.maven.plugin.MojoFailureException; 21 | import org.apache.maven.plugin.PluginParameterExpressionEvaluator; 22 | import org.apache.maven.plugin.logging.Log; 23 | 24 | import com.google.common.base.Joiner; 25 | import com.google.common.collect.Iterables; 26 | import com.google.common.collect.Lists; 27 | import com.google.common.collect.Sets; 28 | import com.itemis.maven.plugins.cdi.CDIMojoProcessingStep; 29 | import com.itemis.maven.plugins.cdi.ExecutionContext; 30 | import com.itemis.maven.plugins.cdi.annotations.ProcessingStep; 31 | import com.itemis.maven.plugins.cdi.annotations.RollbackOnError; 32 | 33 | /** 34 | * An executor for a {@link ProcessingWorkflow} which takes care of executing the steps of the workflow in the correct 35 | * order as well as rolling back the steps in the correct order in case of a failure. 36 | * 37 | * @author Stanley Hillner 38 | * @since 2.0.0 39 | */ 40 | public class WorkflowExecutor { 41 | private Log log; 42 | private ProcessingWorkflow workflow; 43 | private Map processingSteps; 44 | private Stack> executedSteps; 45 | private PluginParameterExpressionEvaluator expressionEvaluator; 46 | 47 | public WorkflowExecutor(ProcessingWorkflow workflow, Map processingSteps, Log log, 48 | PluginParameterExpressionEvaluator expressionEvaluator) { 49 | this.workflow = workflow; 50 | this.processingSteps = processingSteps; 51 | this.log = log; 52 | this.expressionEvaluator = expressionEvaluator; 53 | } 54 | 55 | /** 56 | * Performs a validation of the workflow with respect to the configured set of processing steps this plugin provides. 57 | *
58 | * It is verified that each workflow step has a corresponding implementation providing the same step-id as specified 59 | * in the workflow. 60 | * 61 | * @param isOnlineExecution whether Maven is executed in online mode or not. 62 | * @throws MojoExecutionException if there are missing processing step implementations for one or more ids of the 63 | * workflow. The exception message will list all missing ids. 64 | */ 65 | public void validate(boolean isOnlineExecution) throws MojoExecutionException { 66 | Set unknownIds = Sets.newHashSet(); 67 | Iterable stepsToCheck = Iterables 68 | .unmodifiableIterable(Iterables.concat(this.workflow.getProcessingSteps(), this.workflow.getFinallySteps())); 69 | for (WorkflowStep workflowStep : stepsToCheck) { 70 | if (workflowStep.isParallel()) { 71 | ParallelWorkflowStep parallelWorkflowStep = (ParallelWorkflowStep) workflowStep; 72 | for (SimpleWorkflowStep simpleWorkflowStep : parallelWorkflowStep.getSteps()) { 73 | CDIMojoProcessingStep step = this.processingSteps.get(simpleWorkflowStep.getStepId()); 74 | if (step == null) { 75 | unknownIds.add(simpleWorkflowStep.getStepId()); 76 | } else { 77 | verifyOnlineStatus(step, isOnlineExecution); 78 | } 79 | } 80 | } else { 81 | SimpleWorkflowStep simpleWorkflowStep = (SimpleWorkflowStep) workflowStep; 82 | CDIMojoProcessingStep step = this.processingSteps.get(simpleWorkflowStep.getStepId()); 83 | if (step == null) { 84 | unknownIds.add(simpleWorkflowStep.getStepId()); 85 | } else { 86 | verifyOnlineStatus(step, isOnlineExecution); 87 | } 88 | } 89 | } 90 | 91 | if (!unknownIds.isEmpty()) { 92 | throw new MojoExecutionException( 93 | "There are no implementations for the following processing step ids specified in the workflow: " 94 | + Joiner.on(',').join(unknownIds)); 95 | } 96 | } 97 | 98 | private void verifyOnlineStatus(CDIMojoProcessingStep step, boolean isOnlineExecution) throws MojoExecutionException { 99 | ProcessingStep stepAnnotation = step.getClass().getAnnotation(ProcessingStep.class); 100 | if (stepAnnotation.requiresOnline() && !isOnlineExecution) { 101 | throw new MojoExecutionException( 102 | "The execution of this Mojo requires Maven to operate in online mode but Maven has been started using the offline option."); 103 | } 104 | } 105 | 106 | /** 107 | * Performs the actual workflow execution in the correct order.
108 | * If an exceptional case is reached, all already executed steps will be rolled back prior to throwing the exception. 109 | * 110 | * @throws MojoExecutionException if any of the processing steps of the workflow throw such an exception. 111 | * @throws MojoFailureException if any of the processing steps of the workflow throw such an exception. 112 | */ 113 | public void execute() throws MojoExecutionException, MojoFailureException { 114 | this.log.info("Executing the standard workflow of the goal"); 115 | this.executedSteps = new Stack>(); 116 | 117 | try { 118 | for (WorkflowStep workflowStep : this.workflow.getProcessingSteps()) { 119 | executeSequentialWorkflowStep(workflowStep); 120 | executeParallelWorkflowSteps(workflowStep); 121 | } 122 | } catch (MojoExecutionException e) { 123 | executeFinallySteps(); 124 | throw e; 125 | } catch (MojoFailureException e) { 126 | executeFinallySteps(); 127 | throw e; 128 | } catch (RuntimeException e) { 129 | executeFinallySteps(); 130 | throw e; 131 | } 132 | } 133 | 134 | private void executeFinallySteps() throws MojoExecutionException, MojoFailureException { 135 | if (!this.workflow.getFinallySteps().isEmpty()) { 136 | this.log.info("Executing the finally workflow of the goal"); 137 | this.executedSteps.clear(); 138 | 139 | for (SimpleWorkflowStep step : this.workflow.getFinallySteps()) { 140 | executeSequentialWorkflowStep(step); 141 | } 142 | } 143 | } 144 | 145 | private void executeSequentialWorkflowStep(WorkflowStep workflowStep) 146 | throws MojoExecutionException, MojoFailureException { 147 | if (workflowStep.isParallel()) { 148 | return; 149 | } 150 | 151 | SimpleWorkflowStep simpleWorkflowStep = (SimpleWorkflowStep) workflowStep; 152 | ExecutionContext executionContext = this.workflow.getExecutionContext(simpleWorkflowStep.getCompositeStepId()); 153 | CDIMojoProcessingStep step = this.processingSteps.get(simpleWorkflowStep.getStepId()); 154 | try { 155 | this.executedSteps.push(Pair.of(step, executionContext)); 156 | executionContext.expandProjectVariables(this.expressionEvaluator); 157 | step.execute(executionContext); 158 | } catch (Throwable t) { 159 | this.log.error("An exception was caught while processing the workflow step with id '" 160 | + simpleWorkflowStep.getCompositeStepId() + "'.", t); 161 | rollback(t); 162 | 163 | // throw original exception after rollback! 164 | if (t instanceof MojoExecutionException) { 165 | throw (MojoExecutionException) t; 166 | } else if (t instanceof MojoFailureException) { 167 | throw (MojoFailureException) t; 168 | } else if (t instanceof RuntimeException) { 169 | throw (RuntimeException) t; 170 | } else { 171 | throw new RuntimeException(t); 172 | } 173 | } 174 | } 175 | 176 | private void executeParallelWorkflowSteps(WorkflowStep workflowStep) 177 | throws MojoExecutionException, MojoFailureException { 178 | if (!workflowStep.isParallel()) { 179 | return; 180 | } 181 | 182 | Queue> results = new LinkedList>(); 183 | final Collection thrownExceptions = Lists.newArrayList(); 184 | 185 | ParallelWorkflowStep parallelWorkflowStep = (ParallelWorkflowStep) workflowStep; 186 | ExecutorService executorService = Executors.newFixedThreadPool(parallelWorkflowStep.getSteps().size()); 187 | for (final SimpleWorkflowStep simpleWorkflowStep : parallelWorkflowStep.getSteps()) { 188 | results.offer(executorService.submit(new Runnable() { 189 | @Override 190 | public void run() { 191 | CDIMojoProcessingStep step = WorkflowExecutor.this.processingSteps.get(simpleWorkflowStep.getStepId()); 192 | try { 193 | ExecutionContext executionContext = WorkflowExecutor.this.workflow 194 | .getExecutionContext(simpleWorkflowStep.getCompositeStepId()); 195 | WorkflowExecutor.this.executedSteps.push(Pair.of(step, executionContext)); 196 | executionContext.expandProjectVariables(WorkflowExecutor.this.expressionEvaluator); 197 | step.execute(executionContext); 198 | } catch (Throwable t) { 199 | WorkflowExecutor.this.log.error("An exception was caught while processing the workflow step with id '" 200 | + simpleWorkflowStep.getCompositeStepId() + "'.", t); 201 | thrownExceptions.add(t); 202 | } 203 | } 204 | })); 205 | } 206 | 207 | while (!results.isEmpty()) { 208 | Future result = results.poll(); 209 | try { 210 | result.get(); 211 | } catch (InterruptedException e) { 212 | results.offer(result); 213 | } catch (ExecutionException e) { 214 | // do nothing since this exception will be handled later! 215 | } 216 | } 217 | 218 | Throwable firstError = Iterables.getFirst(thrownExceptions, null); 219 | if (firstError != null) { 220 | rollback(firstError); 221 | // throw original exception after rollback! 222 | if (firstError instanceof MojoExecutionException) { 223 | throw (MojoExecutionException) firstError; 224 | } else if (firstError instanceof MojoFailureException) { 225 | throw (MojoFailureException) firstError; 226 | } else if (firstError instanceof RuntimeException) { 227 | throw (RuntimeException) firstError; 228 | } else { 229 | throw new RuntimeException(firstError); 230 | } 231 | } 232 | } 233 | 234 | private void rollback(Throwable t) { 235 | this.log.info("Rolling back after execution errors - please find the error messages and stack traces above."); 236 | while (!this.executedSteps.empty()) { 237 | Pair pair = this.executedSteps.pop(); 238 | rollback(pair.getLeft(), pair.getRight(), t); 239 | } 240 | } 241 | 242 | private void rollback(CDIMojoProcessingStep step, ExecutionContext executionContext, Throwable t) { 243 | // get rollback methods and sort alphabetically 244 | List rollbackMethods = getRollbackMethods(step, t.getClass()); 245 | Collections.sort(rollbackMethods, new Comparator() { 246 | @Override 247 | public int compare(Method m1, Method m2) { 248 | return m1.getName().compareTo(m2.getName()); 249 | } 250 | }); 251 | 252 | // call rollback methods 253 | for (Method rollbackMethod : rollbackMethods) { 254 | rollbackMethod.setAccessible(true); 255 | try { 256 | Class[] parameterTypes = rollbackMethod.getParameterTypes(); 257 | switch (parameterTypes.length) { 258 | case 0: 259 | rollbackMethod.invoke(step); 260 | break; 261 | case 1: 262 | if (ExecutionContext.class == parameterTypes[0]) { 263 | rollbackMethod.invoke(step, executionContext); 264 | } else { 265 | rollbackMethod.invoke(step, t); 266 | } 267 | break; 268 | case 2: 269 | if (ExecutionContext.class == parameterTypes[0]) { 270 | rollbackMethod.invoke(step, executionContext, t); 271 | } else { 272 | rollbackMethod.invoke(step, t, executionContext); 273 | } 274 | break; 275 | } 276 | } catch (ReflectiveOperationException e) { 277 | this.log.error("An exception was caught while rolling back the workflow step with id '" 278 | + executionContext.getCompositeStepId() + "'. Proceeding with the rollback of the next steps.", e); 279 | } 280 | } 281 | } 282 | 283 | private List getRollbackMethods(CDIMojoProcessingStep mojo, Class causeType) { 284 | List rollbackMethods = Lists.newArrayList(); 285 | for (Method m : mojo.getClass().getDeclaredMethods()) { 286 | RollbackOnError rollbackAnnotation = m.getAnnotation(RollbackOnError.class); 287 | if (rollbackAnnotation != null) { 288 | boolean considerMethod = false; 289 | 290 | // consider method for inclusion if no error types are declared or if one of the declared error types is a 291 | // supertype of the caught exception 292 | Class[] errorTypes = rollbackAnnotation.value(); 293 | if (errorTypes.length == 0) { 294 | considerMethod = true; 295 | } else { 296 | for (Class errorType : errorTypes) { 297 | if (errorType.isAssignableFrom(causeType)) { 298 | considerMethod = true; 299 | break; 300 | } 301 | } 302 | } 303 | 304 | // now check also the method parameters (0 or one exception type) 305 | if (considerMethod) { 306 | Class[] parameterTypes = m.getParameterTypes(); 307 | switch (parameterTypes.length) { 308 | case 0: 309 | rollbackMethods.add(m); 310 | break; 311 | case 1: 312 | if (parameterTypes[0].isAssignableFrom(causeType)) { 313 | rollbackMethods.add(m); 314 | } else if (parameterTypes[0] == ExecutionContext.class) { 315 | rollbackMethods.add(m); 316 | } 317 | break; 318 | case 2: 319 | if (parameterTypes[0] == ExecutionContext.class && parameterTypes[1].isAssignableFrom(causeType)) { 320 | rollbackMethods.add(m); 321 | } else if (parameterTypes[0].isAssignableFrom(causeType) && parameterTypes[1] == ExecutionContext.class) { 322 | rollbackMethods.add(m); 323 | } 324 | break; 325 | default: 326 | this.log.warn( 327 | "Found rollback method with more than two parameters! Only zero, one or two parameters of type and ExecutionContext are allowed!"); 328 | break; 329 | } 330 | } 331 | } 332 | } 333 | 334 | return rollbackMethods; 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/util/workflow/WorkflowStep.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.util.workflow; 2 | 3 | public interface WorkflowStep { 4 | boolean isParallel(); 5 | 6 | boolean containsId(String id); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/util/workflow/WorkflowUtil.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.util.workflow; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.apache.maven.plugin.MojoExecutionException; 15 | import org.apache.maven.plugin.descriptor.PluginDescriptor; 16 | 17 | import com.google.common.base.Objects; 18 | import com.google.common.base.Optional; 19 | import com.google.common.base.Splitter; 20 | import com.google.common.base.Strings; 21 | import com.google.common.collect.Iterables; 22 | import com.google.common.collect.Lists; 23 | import com.google.common.io.ByteStreams; 24 | import com.google.common.io.Closeables; 25 | import com.itemis.maven.plugins.cdi.ExecutionContext; 26 | import com.itemis.maven.plugins.cdi.annotations.ProcessingStep; 27 | import com.itemis.maven.plugins.cdi.internal.util.workflow.ParallelWorkflowStep.Builder; 28 | import com.itemis.maven.plugins.cdi.logging.Logger; 29 | 30 | import de.vandermeer.asciitable.v2.RenderedTable; 31 | import de.vandermeer.asciitable.v2.V2_AsciiTable; 32 | import de.vandermeer.asciitable.v2.render.V2_AsciiTableRenderer; 33 | import de.vandermeer.asciitable.v2.render.WidthLongestLine; 34 | import de.vandermeer.asciitable.v2.row.ContentRow; 35 | import de.vandermeer.asciitable.v2.themes.V2_E_TableThemes; 36 | 37 | /** 38 | * A utility class all around workflow processing, except the actual execution of the workflows. 39 | * 40 | * @author Stanley Hillner 41 | * @since 2.0.0 42 | */ 43 | public class WorkflowUtil { 44 | 45 | public static final String CONTEXT_DATA_MAP_ASSIGNMENT = "=>"; 46 | public static final String CONTEXT_DATA_SEPARATOR = ","; 47 | private static final String DEFAULT_WORKFLOW_DIR = "META-INF/workflows"; 48 | 49 | /** 50 | * Parses a workflow from its descriptor representation. 51 | * 52 | * @param is the input stream to read the workflow descriptor from. This stream will be closed after reading the 53 | * workflow descriptor. 54 | * @param goalName the name of the goal this workflow is designed for. 55 | * @return the parsed processing workflow. 56 | */ 57 | // TODO rework parser! -> too many decision branches! 58 | public static ProcessingWorkflow parseWorkflow(InputStream is, String goalName) { 59 | ProcessingWorkflow workflow = new ProcessingWorkflow(goalName); 60 | 61 | BufferedReader br = null; 62 | try { 63 | Builder parallelStepBuilder = null; 64 | SimpleWorkflowStep currentStep = null; 65 | boolean isTryBlock = false; 66 | boolean isFinallyBlock = false; 67 | 68 | br = new BufferedReader(new InputStreamReader(is)); 69 | String line; 70 | while ((line = br.readLine()) != null) { 71 | line = line.trim(); 72 | if (line.startsWith(WorkflowConstants.KW_COMMENT) || line.isEmpty()) { 73 | continue; 74 | } 75 | 76 | if (line.startsWith(WorkflowConstants.KW_TRY)) { 77 | isTryBlock = true; 78 | isFinallyBlock = false; 79 | } else if (line.startsWith(WorkflowConstants.KW_PARALLEL)) { 80 | parallelStepBuilder = ParallelWorkflowStep.builder(); 81 | } else if (Objects.equal(WorkflowConstants.KW_BLOCK_CLOSE, line)) { 82 | if (currentStep != null) { 83 | currentStep = null; 84 | } else if (isFinallyBlock) { 85 | isFinallyBlock = false; 86 | } else if (parallelStepBuilder != null) { 87 | workflow.addProcessingStep(parallelStepBuilder.build()); 88 | } 89 | } else if (line.startsWith(WorkflowConstants.KW_BLOCK_CLOSE)) { 90 | if (isTryBlock) { 91 | isTryBlock = false; 92 | String substring = line.substring(1).trim(); 93 | if (substring.startsWith(WorkflowConstants.KW_FINALLY) 94 | && substring.endsWith(WorkflowConstants.KW_BLOCK_OPEN)) { 95 | isFinallyBlock = true; 96 | } 97 | } 98 | } else { 99 | if (currentStep == null) { 100 | String id = parseId(line); 101 | Optional qualifier = parseQualifier(line); 102 | SimpleWorkflowStep step = new SimpleWorkflowStep(id, qualifier); 103 | if (line.endsWith(WorkflowConstants.KW_BLOCK_OPEN)) { 104 | currentStep = step; 105 | } 106 | 107 | if (isFinallyBlock) { 108 | workflow.addFinallyStep(step); 109 | } else { 110 | if (parallelStepBuilder == null) { 111 | workflow.addProcessingStep(step); 112 | } else { 113 | parallelStepBuilder.addSteps(step); 114 | } 115 | } 116 | } else { 117 | setDefaultExecutionData(currentStep, line); 118 | setDefaultRollbackData(currentStep, line); 119 | } 120 | } 121 | } 122 | } catch (IOException e) { 123 | throw new RuntimeException("Unable to read the workflow descriptor from the provided input stream.", e); 124 | } finally { 125 | Closeables.closeQuietly(br); 126 | } 127 | 128 | return workflow; 129 | } 130 | 131 | private static String parseId(String line) { 132 | int qualifierOpen = line.indexOf(WorkflowConstants.KW_QUALIFIER_OPEN); 133 | int blockOpen = line.indexOf(WorkflowConstants.KW_BLOCK_OPEN); 134 | int toIndex; 135 | if (qualifierOpen > -1) { 136 | toIndex = qualifierOpen; 137 | } else if (blockOpen > -1) { 138 | toIndex = blockOpen; 139 | } else { 140 | toIndex = line.length(); 141 | } 142 | return line.substring(0, toIndex).trim(); 143 | } 144 | 145 | private static Optional parseQualifier(String line) { 146 | String qualifier = null; 147 | int qualifierOpen = line.indexOf(WorkflowConstants.KW_QUALIFIER_OPEN); 148 | if (qualifierOpen > -1) { 149 | int qualifierClose = line.indexOf(WorkflowConstants.KW_QUALIFIER_CLOSE, qualifierOpen); 150 | qualifier = line.substring(qualifierOpen + 1, qualifierClose); 151 | } 152 | return Optional.fromNullable(qualifier); 153 | } 154 | 155 | private static void setDefaultExecutionData(SimpleWorkflowStep step, String line) { 156 | if (line.startsWith(WorkflowConstants.KW_DATA)) { 157 | int startIndex = line.indexOf(WorkflowConstants.KW_DATA_ASSIGNMENT) + 1; 158 | step.setDefaultExecutionData(line.substring(startIndex).trim()); 159 | } 160 | } 161 | 162 | private static void setDefaultRollbackData(SimpleWorkflowStep step, String line) { 163 | if (line.startsWith(WorkflowConstants.KW_ROLLBACK_DATA)) { 164 | int startIndex = line.indexOf(WorkflowConstants.KW_DATA_ASSIGNMENT) + 1; 165 | step.setDefaultRollbackData(line.substring(startIndex).trim()); 166 | } 167 | } 168 | 169 | public static void addExecutionContexts(ProcessingWorkflow workflow) { 170 | Iterable steps = Iterables 171 | .unmodifiableIterable(Iterables.concat(workflow.getProcessingSteps(), workflow.getFinallySteps())); 172 | for (WorkflowStep step : steps) { 173 | if (step.isParallel()) { 174 | ParallelWorkflowStep parallelStep = (ParallelWorkflowStep) step; 175 | for (SimpleWorkflowStep simpleStep : parallelStep.getSteps()) { 176 | workflow.addExecutionContext(simpleStep.getCompositeStepId(), createExecutionContext(simpleStep)); 177 | } 178 | } else { 179 | SimpleWorkflowStep simpleStep = (SimpleWorkflowStep) step; 180 | workflow.addExecutionContext(simpleStep.getCompositeStepId(), createExecutionContext(simpleStep)); 181 | } 182 | } 183 | } 184 | 185 | private static ExecutionContext createExecutionContext(SimpleWorkflowStep step) { 186 | com.itemis.maven.plugins.cdi.ExecutionContext.Builder builder = ExecutionContext.builder(step.getStepId()); 187 | if (step.getQualifier().isPresent()) { 188 | builder.setQualifier(step.getQualifier().get()); 189 | } 190 | 191 | String dataPropertyValue = System.getProperty(step.getCompositeStepId()); 192 | if (dataPropertyValue == null) { 193 | dataPropertyValue = step.getDefaultExecutionData().orNull(); 194 | } 195 | if (dataPropertyValue != null) { 196 | Iterable split = Splitter.on(CONTEXT_DATA_SEPARATOR).split(dataPropertyValue); 197 | for (String token : split) { 198 | String date = Strings.emptyToNull(token.trim()); 199 | if (date != null) { 200 | List dataSplit = Splitter.on(CONTEXT_DATA_MAP_ASSIGNMENT).splitToList(date); 201 | if (dataSplit.size() == 1) { 202 | builder.addData(dataSplit.get(0)); 203 | } else { 204 | builder.addData(dataSplit.get(0), dataSplit.get(1)); 205 | } 206 | } 207 | } 208 | } 209 | 210 | String dataRollbackPropertyValue = System.getProperty(step.getCompositeStepId() + "-rollback"); 211 | if (dataRollbackPropertyValue == null) { 212 | dataRollbackPropertyValue = step.getDefaultRollbackData().orNull(); 213 | } 214 | if (dataRollbackPropertyValue != null) { 215 | Iterable split = Splitter.on(CONTEXT_DATA_SEPARATOR).split(dataRollbackPropertyValue); 216 | for (String token : split) { 217 | String date = Strings.emptyToNull(token.trim()); 218 | if (date != null) { 219 | List dataSplit = Splitter.on(CONTEXT_DATA_MAP_ASSIGNMENT).splitToList(date); 220 | if (dataSplit.size() == 1) { 221 | builder.addRollbackData(dataSplit.get(0)); 222 | } else { 223 | builder.addRollbackData(dataSplit.get(0), dataSplit.get(1)); 224 | } 225 | } 226 | } 227 | } 228 | 229 | return builder.build(); 230 | } 231 | 232 | public static InputStream getWorkflowDescriptor(String goalName, PluginDescriptor pluginDescriptor, 233 | Optional customWorkflowDescriptor, Logger log) throws MojoExecutionException { 234 | log.info("Constructing workflow for processing"); 235 | String goalPrefix = pluginDescriptor.getGoalPrefix(); 236 | 237 | if (customWorkflowDescriptor.isPresent()) { 238 | File customDescriptor = customWorkflowDescriptor.get(); 239 | log.debug("Requested overriding of workflow with file: " + customDescriptor.getAbsolutePath()); 240 | 241 | if (customDescriptor.exists() && customDescriptor.isFile()) { 242 | try { 243 | log.info("Workflow of goal '" + goalPrefix + ':' + goalName + "' will be overriden by file '" 244 | + customDescriptor.getAbsolutePath() + "'."); 245 | return new FileInputStream(customDescriptor); 246 | } catch (Exception e) { 247 | throw new MojoExecutionException("Unable to load custom workflow for goal " + goalName, e); 248 | } 249 | } else { 250 | throw new MojoExecutionException("Unable to load custom workflow for goal " + goalPrefix + ':' + goalName 251 | + ". The workflow file '" + customDescriptor.getAbsolutePath() + "' does not exist!"); 252 | } 253 | } 254 | 255 | log.info("Goal '" + goalPrefix + ':' + goalName + "' will use default workflow packaged with the plugin."); 256 | return Thread.currentThread().getContextClassLoader().getResourceAsStream(DEFAULT_WORKFLOW_DIR + "/" + goalName); 257 | } 258 | 259 | public static void printWorkflow(String goalName, PluginDescriptor pluginDescriptor, 260 | Optional customWorkflowDescriptor, Logger log) throws MojoExecutionException { 261 | StringBuilder sb = new StringBuilder(); 262 | if (StringUtils.isNotBlank(pluginDescriptor.getGoalPrefix())) { 263 | sb.append(pluginDescriptor.getGoalPrefix()); 264 | } else { 265 | sb.append(pluginDescriptor.getGroupId()).append(':').append(pluginDescriptor.getArtifactId()).append(':') 266 | .append(pluginDescriptor.getVersion()); 267 | } 268 | sb.append(':').append(goalName); 269 | 270 | log.info("Default workflow for '" + sb + "':"); 271 | 272 | InputStream workflowDescriptor = getWorkflowDescriptor(goalName, pluginDescriptor, customWorkflowDescriptor, log); 273 | try { 274 | int x = 77 - goalName.length(); 275 | int a = x / 2; 276 | int b = x % 2 == 1 ? a + 1 : a; 277 | StringBuilder separator = new StringBuilder(); 278 | separator.append(Strings.repeat("=", a)).append(' ').append(goalName).append(' ').append(Strings.repeat("=", b)); 279 | 280 | System.out.println(separator); 281 | ByteStreams.copy(workflowDescriptor, System.out); 282 | System.out.println(separator); 283 | } catch (IOException e) { 284 | throw new MojoExecutionException("A problem occurred during the serialization of the defualt workflow.", e); 285 | } finally { 286 | Closeables.closeQuietly(workflowDescriptor); 287 | } 288 | } 289 | 290 | public static boolean printAvailableSteps(Map steps, Logger log) 291 | throws MojoExecutionException { 292 | V2_AsciiTable table = new V2_AsciiTable(); 293 | table.addRule(); 294 | ContentRow header = table.addRow("ID", "DESCRIPTION", "REQUIRES ONLINE"); 295 | header.setAlignment(new char[] { 'c', 'c', 'c' }); 296 | table.addStrongRule(); 297 | 298 | List sortedIds = Lists.newArrayList(steps.keySet()); 299 | Collections.sort(sortedIds); 300 | for (String id : sortedIds) { 301 | ProcessingStep annotation = steps.get(id); 302 | ContentRow data = table.addRow(annotation.id(), annotation.description(), annotation.requiresOnline()); 303 | data.setAlignment(new char[] { 'l', 'l', 'c' }); 304 | table.addRule(); 305 | } 306 | 307 | V2_AsciiTableRenderer renderer = new V2_AsciiTableRenderer(); 308 | renderer.setTheme(V2_E_TableThemes.UTF_STRONG_DOUBLE.get()); 309 | renderer.setWidth(new WidthLongestLine().add(10, 20).add(20, 50).add(10, 10)); 310 | RenderedTable renderedTable = renderer.render(table); 311 | 312 | log.info( 313 | "The following processing steps are available on classpath and can be configured as part of a custom workflow."); 314 | System.out.println(renderedTable); 315 | 316 | return true; 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/internal/util/workflow/WorkflowValidator.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.internal.util.workflow; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | 8 | import com.google.common.io.Closeables; 9 | 10 | /** 11 | * A utility class for workflow validation. 12 | * 13 | * @author Stanley Hillner 14 | * @since 3.2.0 15 | */ 16 | public class WorkflowValidator { 17 | 18 | public static void validateSyntactically(InputStream is) { 19 | BufferedReader br = null; 20 | try { 21 | boolean isTryBlockOpen = false; 22 | boolean isFinallyBlockOpen = true; 23 | boolean isParallelBlockOpen = false; 24 | boolean isDataAssignmentBlockOpen = false; 25 | 26 | br = new BufferedReader(new InputStreamReader(is)); 27 | String line; 28 | int lineNumber = 0; 29 | while ((line = br.readLine()) != null) { 30 | line = line.trim(); 31 | if (line.startsWith(WorkflowConstants.KW_COMMENT) || line.isEmpty()) { 32 | continue; 33 | } 34 | // line number is only increased for non-comment lines 35 | lineNumber++; 36 | 37 | isTryBlockOpen = validateTryBlockOpening(line, lineNumber); 38 | isFinallyBlockOpen = validateFinallyBlockOpening(line, isTryBlockOpen); 39 | if (isFinallyBlockOpen) { 40 | isTryBlockOpen = false; 41 | } 42 | } 43 | } catch (IOException e) { 44 | throw new RuntimeException("Unable to read the workflow descriptor from the provided input stream.", e); 45 | } finally { 46 | Closeables.closeQuietly(br); 47 | } 48 | } 49 | 50 | private static boolean validateTryBlockOpening(String line, int lineNumber) { 51 | if (line.contains(WorkflowConstants.KW_TRY)) { 52 | if (!line.startsWith(WorkflowConstants.KW_TRY)) { 53 | throw new RuntimeException( 54 | "Opening the try-block requires the keyword 'try' to be the first token of the line. Processed line was: '" 55 | + line + "'"); 56 | } 57 | if (lineNumber != 1) { 58 | throw new RuntimeException( 59 | "The try-block opening must be the first statement. Only comments are allowed to occur before opening the try-block. Processed line was: '" 60 | + line + "'"); 61 | } 62 | String remainingContent = line.substring(WorkflowConstants.KW_TRY.length()).trim(); 63 | if (!remainingContent.equals(WorkflowConstants.KW_BLOCK_OPEN)) { 64 | throw new RuntimeException("The try block opening must end with the block opening character '" 65 | + WorkflowConstants.KW_BLOCK_OPEN + "'. Processed line was: '" + line + "'"); 66 | } 67 | return true; 68 | } 69 | return false; 70 | } 71 | 72 | private static boolean validateFinallyBlockOpening(String line, boolean isTryBlockOpen) { 73 | if (line.startsWith(WorkflowConstants.KW_BLOCK_CLOSE)) { 74 | String remainingContent = line.substring(1).trim(); 75 | if (line.startsWith(WorkflowConstants.KW_FINALLY)) { 76 | remainingContent = line.substring(WorkflowConstants.KW_FINALLY.length()).trim(); 77 | if (!remainingContent.equals(WorkflowConstants.KW_BLOCK_OPEN)) { 78 | throw new RuntimeException("The finally block opening must end with the block opening character '" 79 | + WorkflowConstants.KW_BLOCK_OPEN + "'. Processed line was: '" + line + "'"); 80 | } 81 | return true; 82 | } 83 | } 84 | return false; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/logging/Logger.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.logging; 2 | 3 | import org.apache.maven.plugin.logging.Log; 4 | 5 | public interface Logger extends Log { 6 | 7 | void setContextClass(Class contextClass); 8 | 9 | void unsetContext(); 10 | 11 | void enableLogTimestamps(); 12 | 13 | void disableLogTimestamps(); 14 | 15 | boolean isTimestampedLoggingEnabled(); 16 | } -------------------------------------------------------------------------------- /src/main/java/com/itemis/maven/plugins/cdi/logging/MavenLogWrapper.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.logging; 2 | 3 | import java.text.DateFormat; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | import javax.enterprise.inject.Typed; 8 | 9 | import org.apache.maven.plugin.logging.Log; 10 | 11 | import com.google.common.base.Preconditions; 12 | 13 | /** 14 | * A wrapper around the maven logger that offers some extended functionalities like checking the enablement of the 15 | * appropriate log level before logging. 16 | * 17 | * @author Stanley Hillner 18 | * 19 | */ 20 | @Typed({ Logger.class, Log.class }) 21 | public class MavenLogWrapper implements Logger { 22 | private static final DateFormat FORMAT_TIMESTAMP = new SimpleDateFormat("HH:mm:ss,SSS "); 23 | 24 | private Log log; 25 | private String context; 26 | private boolean timestampsEnabled; 27 | 28 | public MavenLogWrapper(Log log) { 29 | this.log = log; 30 | } 31 | 32 | @Override 33 | public void setContextClass(Class contextClass) { 34 | Preconditions.checkNotNull(contextClass, "The context class for the logger must not be null!"); 35 | this.context = contextClass.getSimpleName(); 36 | } 37 | 38 | @Override 39 | public void unsetContext() { 40 | this.context = null; 41 | } 42 | 43 | public boolean hasContext() { 44 | return this.context != null; 45 | } 46 | 47 | @Override 48 | public void enableLogTimestamps() { 49 | this.timestampsEnabled = true; 50 | } 51 | 52 | @Override 53 | public void disableLogTimestamps() { 54 | this.timestampsEnabled = false; 55 | } 56 | 57 | @Override 58 | public boolean isTimestampedLoggingEnabled() { 59 | return this.timestampsEnabled; 60 | } 61 | 62 | @Override 63 | public boolean isDebugEnabled() { 64 | return this.log.isDebugEnabled(); 65 | } 66 | 67 | @Override 68 | public void debug(CharSequence content) { 69 | if (this.log.isDebugEnabled()) { 70 | this.log.debug(wrapContent(content)); 71 | } 72 | } 73 | 74 | @Override 75 | public void debug(CharSequence content, Throwable error) { 76 | if (this.log.isDebugEnabled()) { 77 | this.log.debug(wrapContent(content), error); 78 | } 79 | } 80 | 81 | @Override 82 | public void debug(Throwable error) { 83 | if (this.log.isDebugEnabled()) { 84 | this.log.debug(error); 85 | } 86 | } 87 | 88 | @Override 89 | public boolean isInfoEnabled() { 90 | return this.log.isInfoEnabled(); 91 | } 92 | 93 | @Override 94 | public void info(CharSequence content) { 95 | if (this.log.isInfoEnabled()) { 96 | this.log.info(wrapContent(content)); 97 | } 98 | } 99 | 100 | @Override 101 | public void info(CharSequence content, Throwable error) { 102 | if (this.log.isInfoEnabled()) { 103 | this.log.info(wrapContent(content), error); 104 | } 105 | } 106 | 107 | @Override 108 | public void info(Throwable error) { 109 | if (this.log.isInfoEnabled()) { 110 | this.log.info(error); 111 | } 112 | } 113 | 114 | @Override 115 | public boolean isWarnEnabled() { 116 | return this.log.isWarnEnabled(); 117 | } 118 | 119 | @Override 120 | public void warn(CharSequence content) { 121 | if (this.log.isWarnEnabled()) { 122 | this.log.warn(wrapContent(content)); 123 | } 124 | } 125 | 126 | @Override 127 | public void warn(CharSequence content, Throwable error) { 128 | if (this.log.isWarnEnabled()) { 129 | this.log.warn(wrapContent(content), error); 130 | } 131 | } 132 | 133 | @Override 134 | public void warn(Throwable error) { 135 | if (this.log.isWarnEnabled()) { 136 | this.log.warn(error); 137 | } 138 | } 139 | 140 | @Override 141 | public boolean isErrorEnabled() { 142 | return this.log.isErrorEnabled(); 143 | } 144 | 145 | @Override 146 | public void error(CharSequence content) { 147 | if (this.log.isErrorEnabled()) { 148 | this.log.error(wrapContent(content)); 149 | } 150 | } 151 | 152 | @Override 153 | public void error(CharSequence content, Throwable error) { 154 | if (this.log.isErrorEnabled()) { 155 | this.log.error(wrapContent(content), error); 156 | } 157 | } 158 | 159 | @Override 160 | public void error(Throwable error) { 161 | if (this.log.isErrorEnabled()) { 162 | this.log.error(error); 163 | } 164 | } 165 | 166 | private CharSequence wrapContent(CharSequence content) { 167 | StringBuilder sb = new StringBuilder(content); 168 | wrapContentWithContext(sb); 169 | wrapContentWithTimestamp(sb); 170 | return sb; 171 | } 172 | 173 | private void wrapContentWithContext(StringBuilder content) { 174 | if (this.context != null) { 175 | content.insert(0, "[" + this.context + "] "); 176 | } 177 | } 178 | 179 | private void wrapContentWithTimestamp(StringBuilder content) { 180 | if (this.timestampsEnabled) { 181 | String date = FORMAT_TIMESTAMP.format(new Date()); 182 | content.insert(0, date + " "); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/test/java/com/itemis/maven/plugins/cdi/util/WorkflowUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.util; 2 | 3 | import java.io.InputStream; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | import com.google.common.base.Strings; 11 | import com.itemis.maven.plugins.cdi.internal.util.workflow.ParallelWorkflowStep; 12 | import com.itemis.maven.plugins.cdi.internal.util.workflow.ProcessingWorkflow; 13 | import com.itemis.maven.plugins.cdi.internal.util.workflow.SimpleWorkflowStep; 14 | import com.itemis.maven.plugins.cdi.internal.util.workflow.WorkflowStep; 15 | import com.itemis.maven.plugins.cdi.internal.util.workflow.WorkflowUtil; 16 | 17 | public class WorkflowUtilTest { 18 | 19 | @Test 20 | public void testParseWorkflow_Sequential() { 21 | ProcessingWorkflow workflow = WorkflowUtil.parseWorkflow(getWorkflowAsStream("sequential"), "wf1"); 22 | 23 | Assert.assertEquals("wf1", workflow.getGoal()); 24 | for (WorkflowStep step : workflow.getProcessingSteps()) { 25 | Assert.assertFalse("The workflow contains at least one parallel step although all steps should be sequential!", 26 | step.isParallel()); 27 | Assert.assertEquals("Workflow step of wrong type", SimpleWorkflowStep.class, step.getClass()); 28 | SimpleWorkflowStep s = (SimpleWorkflowStep) step; 29 | Assert.assertNotNull( 30 | "The processing step id doesn't seem to be set correctly for the sequential processing step.", 31 | Strings.emptyToNull(s.getStepId())); 32 | } 33 | } 34 | 35 | @Test 36 | public void testParseWorkflow_Parallel() { 37 | ProcessingWorkflow workflow = WorkflowUtil.parseWorkflow(getWorkflowAsStream("parallel"), "wf2"); 38 | 39 | Assert.assertEquals("wf2", workflow.getGoal()); 40 | // The first step is a parallel step 41 | WorkflowStep parallelStep = workflow.getProcessingSteps().get(0); 42 | Assert.assertTrue("The first processing step should be a parallel one.", parallelStep.isParallel()); 43 | Assert.assertEquals("Workflow step of wrong type", ParallelWorkflowStep.class, parallelStep.getClass()); 44 | ParallelWorkflowStep s = (ParallelWorkflowStep) parallelStep; 45 | Assert.assertFalse("The parallel step should contain a list of parallel step ids.", s.getSteps().isEmpty()); 46 | 47 | for (int i = 1; i < workflow.getProcessingSteps().size(); i++) { 48 | WorkflowStep step = workflow.getProcessingSteps().get(i); 49 | Assert.assertFalse( 50 | "The workflow contains a further parallel steps although all but the first step should be sequential!", 51 | step.isParallel()); 52 | } 53 | } 54 | 55 | @Test 56 | public void testParseWorkflow_Sequential_Qualifiers() { 57 | ProcessingWorkflow workflow = WorkflowUtil.parseWorkflow(getWorkflowAsStream("sequential_qualifiers"), "wf3"); 58 | 59 | Assert.assertEquals("wf3", workflow.getGoal()); 60 | for (WorkflowStep step : workflow.getProcessingSteps()) { 61 | Assert.assertEquals("Workflow step of wrong type", SimpleWorkflowStep.class, step.getClass()); 62 | SimpleWorkflowStep s = (SimpleWorkflowStep) step; 63 | Assert.assertNotNull( 64 | "The processing step id doesn't seem to be set correctly for the sequential processing step.", 65 | Strings.emptyToNull(s.getStepId())); 66 | Assert.assertTrue( 67 | "The processing step qualifier doesn't seem to be set correctly for the sequential processing step.", 68 | s.getQualifier().isPresent()); 69 | } 70 | } 71 | 72 | @Test 73 | public void testParseWorkflow_Sequential_Data() { 74 | ProcessingWorkflow workflow = WorkflowUtil.parseWorkflow(getWorkflowAsStream("sequential_data"), "wf4"); 75 | 76 | Assert.assertEquals("wf4", workflow.getGoal()); 77 | for (WorkflowStep step : workflow.getProcessingSteps()) { 78 | SimpleWorkflowStep s = (SimpleWorkflowStep) step; 79 | if ("check3".equals(s.getStepId())) { 80 | Assert.assertTrue( 81 | "No default execution data was parsed from the workflow for step with id '" + s.getStepId() + "'", 82 | s.getDefaultExecutionData().isPresent()); 83 | Assert.assertTrue( 84 | "No default rollback data was parsed from the workflow for step with id '" + s.getStepId() + "'", 85 | s.getDefaultRollbackData().isPresent()); 86 | Assert.assertEquals( 87 | "The parsed default execution data of step '" + s.getStepId() + "' differs from the expected result.", 88 | "xyz, abc", s.getDefaultExecutionData().get()); 89 | Assert.assertEquals( 90 | "The parsed default rollback data of step '" + s.getStepId() + "' differs from the expected result.", "123", 91 | s.getDefaultRollbackData().get()); 92 | } 93 | } 94 | } 95 | 96 | @Test 97 | public void testParseWorkflow_Parallel_Data() { 98 | ProcessingWorkflow workflow = WorkflowUtil.parseWorkflow(getWorkflowAsStream("parallel_data"), "wf5"); 99 | 100 | Assert.assertEquals("wf5", workflow.getGoal()); 101 | // The first step is a parallel step 102 | WorkflowStep parallelStep = workflow.getProcessingSteps().get(0); 103 | Assert.assertTrue("The first processing step should be a parallel one.", parallelStep.isParallel()); 104 | Assert.assertEquals("Workflow step of wrong type", ParallelWorkflowStep.class, parallelStep.getClass()); 105 | ParallelWorkflowStep s = (ParallelWorkflowStep) parallelStep; 106 | Assert.assertFalse("The parallel step should contain a list of parallel step ids.", s.getSteps().isEmpty()); 107 | 108 | for (int i = 1; i < workflow.getProcessingSteps().size(); i++) { 109 | SimpleWorkflowStep step = (SimpleWorkflowStep) workflow.getProcessingSteps().get(i); 110 | if ("check1".equals(step.getStepId())) { 111 | Assert.assertTrue( 112 | "No default execution data was parsed from the workflow for step with id '" + step.getStepId() + "'", 113 | step.getDefaultExecutionData().isPresent()); 114 | Assert.assertFalse("Default rollback data was parsed from the workflow for step with id '" + step.getStepId() 115 | + "' but nothing was configured!", step.getDefaultRollbackData().isPresent()); 116 | Assert.assertEquals( 117 | "The parsed default execution data of step '" + step.getStepId() + "' differs from the expected result.", 118 | "test", step.getDefaultExecutionData().get()); 119 | } else if ("check2[y]".equals(step.getCompositeStepId())) { 120 | Assert.assertTrue( 121 | "No default execution data was parsed from the workflow for step with id '" + step.getStepId() + "'", 122 | step.getDefaultExecutionData().isPresent()); 123 | Assert.assertTrue( 124 | "No default rollback data was parsed from the workflow for step with id '" + step.getStepId() + "'", 125 | step.getDefaultRollbackData().isPresent()); 126 | Assert.assertEquals( 127 | "The parsed default execution data of step '" + step.getStepId() + "' differs from the expected result.", 128 | "xyz, test=>123", step.getDefaultExecutionData().get()); 129 | Assert.assertEquals( 130 | "The parsed default rollback data of step '" + step.getStepId() + "' differs from the expected result.", 131 | "a=>1 ,b=>2", step.getDefaultRollbackData().get()); 132 | } 133 | } 134 | } 135 | 136 | @Test 137 | public void testParseWorkflow_TryFinally() { 138 | ProcessingWorkflow workflow = WorkflowUtil.parseWorkflow(getWorkflowAsStream("try-finally"), "wf6"); 139 | 140 | Assert.assertEquals("wf6", workflow.getGoal()); 141 | List trySteps = workflow.getProcessingSteps(); 142 | Assert.assertEquals("Expected the standard workflow to have exactly 3 processing steps.", 3, trySteps.size()); 143 | Assert.assertEquals("step1[1]", ((SimpleWorkflowStep) trySteps.get(0)).getCompositeStepId()); 144 | Assert.assertEquals("step2", ((SimpleWorkflowStep) trySteps.get(1)).getCompositeStepId()); 145 | Assert.assertEquals("step1[2]", ((SimpleWorkflowStep) trySteps.get(2)).getCompositeStepId()); 146 | 147 | List finallySteps = workflow.getFinallySteps(); 148 | Assert.assertEquals("Expected the finally workflow to have exactly 2 processing steps.", 2, finallySteps.size()); 149 | Assert.assertEquals("step1[3]", finallySteps.get(0).getCompositeStepId()); 150 | Assert.assertEquals("step3", finallySteps.get(1).getCompositeStepId()); 151 | } 152 | 153 | @Test 154 | public void testParseWorkflow_TryFinally_Complex() { 155 | ProcessingWorkflow workflow = WorkflowUtil.parseWorkflow(getWorkflowAsStream("try-finally_complex"), "wf7"); 156 | 157 | Assert.assertEquals("wf7", workflow.getGoal()); 158 | List trySteps = workflow.getProcessingSteps(); 159 | Assert.assertEquals("Expected the standard workflow to have exactly 2 processing steps.", 2, trySteps.size()); 160 | 161 | Assert.assertFalse("Expected a sequential step as the first one.", trySteps.get(0).isParallel()); 162 | SimpleWorkflowStep step1 = (SimpleWorkflowStep) trySteps.get(0); 163 | Assert.assertEquals("step1[1]", step1.getCompositeStepId()); 164 | Assert.assertTrue("Step 1 should have default execution data assigned.", 165 | step1.getDefaultExecutionData().isPresent()); 166 | Assert.assertEquals("echo test", step1.getDefaultExecutionData().get()); 167 | Assert.assertTrue("Step 1 should have default rollback data assigned.", step1.getDefaultRollbackData().isPresent()); 168 | Assert.assertEquals("echo 123", step1.getDefaultRollbackData().get()); 169 | 170 | Assert.assertTrue("Expected a parallel step as the second one.", trySteps.get(1).isParallel()); 171 | ParallelWorkflowStep step2 = (ParallelWorkflowStep) trySteps.get(1); 172 | Set parallelSteps = step2.getSteps(); 173 | Assert.assertEquals("Expected two steps to be executed in parallel.", 2, parallelSteps.size()); 174 | 175 | for (SimpleWorkflowStep step : parallelSteps) { 176 | Assert.assertTrue("step2".equals(step.getCompositeStepId()) || "step1[2]".equals(step.getCompositeStepId())); 177 | if ("step2".equals(step.getCompositeStepId())) { 178 | Assert.assertTrue("The first parallel step should have default execution data assigned.", 179 | step.getDefaultExecutionData().isPresent()); 180 | Assert.assertEquals("abc", step.getDefaultExecutionData().get()); 181 | Assert.assertFalse("The first parallel step should not have default rollback data assigned.", 182 | step.getDefaultRollbackData().isPresent()); 183 | } else { 184 | Assert.assertFalse("The second parallel step should not have default execution data assigned.", 185 | step.getDefaultExecutionData().isPresent()); 186 | Assert.assertFalse("The second parallel step should not have default rollback data assigned.", 187 | step.getDefaultRollbackData().isPresent()); 188 | } 189 | } 190 | 191 | List finallySteps = workflow.getFinallySteps(); 192 | Assert.assertEquals("Expected the finally workflow to have exactly 2 processing steps.", 2, finallySteps.size()); 193 | 194 | SimpleWorkflowStep fStep1 = finallySteps.get(0); 195 | Assert.assertEquals("step1[3]", fStep1.getCompositeStepId()); 196 | Assert.assertFalse("The first finally step should not have default execution data assigned.", 197 | fStep1.getDefaultExecutionData().isPresent()); 198 | Assert.assertTrue("The first finally step should have default rollback data assigned.", 199 | fStep1.getDefaultRollbackData().isPresent()); 200 | 201 | SimpleWorkflowStep fStep2 = finallySteps.get(1); 202 | Assert.assertEquals("step3", fStep2.getCompositeStepId()); 203 | Assert.assertFalse("The first finally step should not have default execution data assigned.", 204 | fStep2.getDefaultExecutionData().isPresent()); 205 | Assert.assertFalse("The first finally step should not have default rollback data assigned.", 206 | fStep2.getDefaultRollbackData().isPresent()); 207 | } 208 | 209 | private InputStream getWorkflowAsStream(String name) { 210 | return Thread.currentThread().getContextClassLoader().getResourceAsStream("workflows/" + name); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/test/java/com/itemis/maven/plugins/cdi/util/WorkflowValidatorTest.java: -------------------------------------------------------------------------------- 1 | package com.itemis.maven.plugins.cdi.util; 2 | 3 | import java.io.InputStream; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.rules.ExpectedException; 8 | import org.junit.runner.RunWith; 9 | 10 | import com.itemis.maven.plugins.cdi.internal.util.workflow.WorkflowValidator; 11 | import com.tngtech.java.junit.dataprovider.DataProvider; 12 | import com.tngtech.java.junit.dataprovider.DataProviderRunner; 13 | 14 | @RunWith(DataProviderRunner.class) 15 | public class WorkflowValidatorTest { 16 | @Rule 17 | public ExpectedException exception = ExpectedException.none(); 18 | 19 | @Test 20 | @DataProvider({ "try-finally", "try-finally_complex" }) 21 | public void testValidate(String workflowName) { 22 | WorkflowValidator.validateSyntactically(getWorkflowAsStream(workflowName)); 23 | } 24 | 25 | @Test 26 | @DataProvider({ "invalid/try-finally_noTryBlockOpening", "invalid/try-finally_noTryBlockClosing", 27 | "invalid/try-finally_noFinallyBlock" }) 28 | public void testValidate_error(String workflowName) { 29 | this.exception.expect(RuntimeException.class); 30 | WorkflowValidator.validateSyntactically(getWorkflowAsStream(workflowName)); 31 | } 32 | 33 | private InputStream getWorkflowAsStream(String name) { 34 | return Thread.currentThread().getContextClassLoader().getResourceAsStream("workflows/" + name); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/resources/workflows/invalid/try-finally-noFinallyBlock: -------------------------------------------------------------------------------- 1 | try 2 | step1[1] 3 | step2 4 | step1[2] 5 | } -------------------------------------------------------------------------------- /src/test/resources/workflows/invalid/try-finally-noTryBlockClosing: -------------------------------------------------------------------------------- 1 | try { 2 | step1[1] 3 | step2 4 | step1[2] 5 | -------------------------------------------------------------------------------- /src/test/resources/workflows/invalid/try-finally_noTryBlockOpening: -------------------------------------------------------------------------------- 1 | try 2 | step1[1] 3 | step2 4 | step1[2] -------------------------------------------------------------------------------- /src/test/resources/workflows/parallel: -------------------------------------------------------------------------------- 1 | parallel { 2 | check1 3 | check2 4 | check3 5 | } 6 | perform1 7 | perform2 8 | verify -------------------------------------------------------------------------------- /src/test/resources/workflows/parallel_data: -------------------------------------------------------------------------------- 1 | parallel { 2 | check1{ 3 | data = test 4 | } 5 | check2[x] 6 | check2[y]{ 7 | data=xyz, test=>123 8 | rollbackData =a=>1 ,b=>2 9 | } 10 | check3 11 | } 12 | perform1 13 | perform2 14 | verify -------------------------------------------------------------------------------- /src/test/resources/workflows/sequential: -------------------------------------------------------------------------------- 1 | check1 2 | check2 3 | check3 4 | perform1 5 | perform2 6 | verify -------------------------------------------------------------------------------- /src/test/resources/workflows/sequential_data: -------------------------------------------------------------------------------- 1 | check1 2 | check2 3 | check3 { 4 | data = xyz, abc 5 | rollbackData=123 6 | } 7 | perform1 8 | perform2 9 | verify -------------------------------------------------------------------------------- /src/test/resources/workflows/sequential_qualifiers: -------------------------------------------------------------------------------- 1 | check[1] 2 | check[2] -------------------------------------------------------------------------------- /src/test/resources/workflows/try-finally: -------------------------------------------------------------------------------- 1 | try { 2 | step1[1] 3 | step2 4 | step1[2] 5 | } finally { 6 | step1[3] 7 | step3 8 | } -------------------------------------------------------------------------------- /src/test/resources/workflows/try-finally_complex: -------------------------------------------------------------------------------- 1 | try { 2 | step1[1] { 3 | data = echo test 4 | rollbackData = echo 123 5 | } 6 | parallel { 7 | step2{ 8 | data=abc 9 | } 10 | step1[2] 11 | } 12 | } finally { 13 | step1[3]{ 14 | rollbackData=testRollback 15 | } 16 | step3 17 | } --------------------------------------------------------------------------------