I assume everyone knows what Minecraft is, unless you just came back from 3 years journey from Mars.
Personally I don't play it, but noticed how vast the worlds are, how many events occur, and how terribly slow the current server implementation is.
So I was thinking, it would be a nice showcase for our technologies - especially fast clustered messaging (HornetQ) and cache (Infinispan).
Perhaps a keynote demo for the next year :-)
Run this script from Cron:
SERVER=irc.freenode.org CHANNEL=mychannel NICK=mynick git pull git log -4 --pretty=format:'%h - %an : %s (%cr)' > commits.txt function announceCommits(){ ( echo NICK $NICK echo USER $NICK 8 \* : Notifier #echo "JOIN #$CHANNEL" while read -r LINE ; do #echo "PRIVMSG #$CHANNEL $LINE" >&2 echo "PRIVMSG #$CHANNEL :$LINE" sleep 1 done < commits.txt echo QUIT ) | nc -v $SERVER 6667 } announceCommits
What's missing is detection of last announced commit.
That would go along the lines of keeping the last announed commit hash in a file, and only dumping the successors.
That should work with `git log --since`, but doesn't. I didn't want to spend more time fixing that as Jenkins does this for me (git poll build trigger).
The biggest news in the SwanLoom project (to be renamed to WindRide) is that the migration rules are now externalized. But:
Answers to these questions can be found in this introductory video.
More videos here.
Sorry for my accent, I am blushing when I hear it. Still - enjoy :-)
Here's the text, for SEO purposes.
Hello. I'm Ondra Žižka, and I will introduce the latest feature of WindRide -
which is a tool for migrating the configuration of JBoss Enterprise Application Platform (or EAP)
from version 5 to version 6. (Support for other servers will be added later.)
The latest feature is that the migration logic is externalized into XML files.
Let's see how it works.
The big picture is:
1. Query the source server for collection of information,
2. Iterate over these collections,
3. Take the actions based on what is found.
Now let's see how to define that.
The XML file contains a definition of so-called migrator, which is a logical group of actions covering certain configuration area, for example, datasources.
Migrator has a name which is then used by the program to refer to it.
It contains JAXB beans declarations, queries, and actions.
JAXB beans declarations tell the program what classes will be used for unmarshalling the configuration.
Since all of the application servers store their configuration mostly in XML files, JAXB is a natural choice.
Then comes an example of a query; in this case, it's an XML query. There are more types, for example, properties file query.
Different types target different ways of how configuration is stored in the source server.
More details about queries can be found in the project's documentation.
All queries have an ID, by which their result is referred in the actions.
Subject label is used in user interface, and for the HTML reports and error messages.
Other query attributes are specific for the particular query type.
Actions define the actual steps taken during migration.
Again, there are different types, specified by the `type` attribute.
Few types are built in, like CLI command action, XSLT transform action, file copy, and so on.
Custom actions may be implemented either in Java or Groovy. See the documentation for a how to.
A reference to the action may be stored in a variable.
If you are familiar with Ant, you can think of the action as an Ant task.
Typically, you need to migrate multiple configuration items.
These items are stored in collections returned by the aforementioned queries.
To iterate over the collections, the <forEach> element is used.
You refer the query, and declare the name of variable to store the item for each iteration step.
Inside a loop, you may declare another loop, or an action.
This way, in cobination with queries, you have quite powerful tools at hand.
But that's not all.
As you probably noticed, there are expressions inside the values.
The syntax used is Java Expression Language 2.0.
The values available for the expression language are documented, but generally, you can use:
* application's configuration
* application's arguments
* queries' results
* Any parent in the nested constructs (like <forEach> and <action>)
The <filter> element is a condition in a form of a Groovy expression
which controls whether given construct will be processed for the current iteration.
The same variables are available for the script as for the expressions.
An action nested in another action creates a dependency of the parent action.
The inner action be performed prior to the parent action,
and if it fails, parent action also fails.
Lastly, the warning element attaches a warning to an action.
Each action may have a list of text messages, which are presented to the users
when their manual intervention is needed.
Typically, in these cases:
* Migration of certain configration item is not supported (yet)
* Migration is not possible (for example, there's no equivalent in the target server)
* Certain discrepancy or inconsistency was found in the source server configuration.
So these are the externalized rules in a nutshell.
And as you can see, the combination of instruments gives you quite powerful tool.
Please refer to the project's documentation to find out
about all the implemented JAXB beans, queries, actions, and also how to implement your own,
which will be also covered by the upcoming videos.
See you soon!
I have a JBoss EAP server on a OpenShift. And occassionaly, a situation occurs which makes the app start throwing exceptions with lenghty stack traces on each request. That causes the logs to reach dozens of MB per day, and after some time, the hosting runs out of disk space.
I couldn't find something which would take the exception and tell me if the same exception was already seen or not. Application wide. Could be expiring cache.
I recall I've seen some RepeatedExceptionsHandler
or something such for Log4j which I use, or maybe it was Hibernate, but can't find it in a quick google search.
So here's my solution. Just a first sketch but works.
catch( Exception ex ){ int count = repeatedExDetector.countException( ex ); if( count < 2 ) log.error("Error rendering: " + path, ex); else log.error("Recurring rendering error: " + path); add( new TexyDocumentErrorPanel("document", "Error occured when loading document.", "Couldn't load: " + path, 500)); }
02:23:18,324 DEBUG Characteristic: 46d21ef0{8a5b1c7c@293{b0d929ae@276{b0d929ae@182{de8e612a@241 02:23:18,324 ERROR Error rendering: /programovani/artificial_intelligence/covering_ai_theory.texy: java.lang.UnsupportedOperationException: Definition lists not supported yet. at cz.dynawest.jtexy.modules.ListModule$DefListPatternHandler.handle(ListModule.java:293) ... 02:23:19,956 DEBUG Characteristic: 46d21ef0{8a5b1c7c@293{b0d929ae@276{b0d929ae@182{de8e612a@241 02:23:19,957 ERROR Recurring rendering error: /programovani/artificial_intelligence/covering_ai_theory.texy
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Counts the occurrences of given exception. * Uses messages hashes and first 3 stack elements class name hashes to determine "same" exception. * * @author Ondrej Zizka, ozizka at redhat.com */ public class RepeatedExceptionsDetector { private static final Logger log = LoggerFactory.getLogger( RepeatedExceptionsDetector.class ); private final Map<String, Integer> exCount = new ConcurrentHashMap(); private static final int STACKTRACE_SCAN_DEPTH = 3; /** * Counts how many times given exception was seen. */ public int countException( Exception ex ){ String chara = buildCharacteristicsString( ex ); log.info("Characteristic: " + chara); Integer count = this.exCount.get( chara ); if( count == null ) count = new Integer(1); else count++; this.exCount.put( chara, count ); return count; } /** * Builds a string which can be considered as a hash of given exception. */ private static String buildCharacteristicsString( Exception ex ) { StringBuilder sb = new StringBuilder(); Throwable curEx = ex; do{ sb.append( Integer.toHexString( curEx.getMessage().hashCode() ) ); StackTraceElement[] stackTrace = curEx.getStackTrace(); // For each stack trace element, append "cFooClass@72". for( int i = 0; i < stackTrace.length & i <= STACKTRACE_SCAN_DEPTH; i++ ) { StackTraceElement ste = stackTrace[i]; sb.append('{'); sb.append( Integer.toHexString( ste.getClassName().hashCode() ) ); sb.append('@'); sb.append( ste.getLineNumber() ); } sb.append("\n"); curEx = curEx.getCause(); }while( curEx != null ); return sb.toString(); } }// class
It's quite interesting that after few years of existence of CDI, there's still no runtime debugging tool (correct me if I'm wrong).
But that's going to change! Check the promising CDI Inspector project, a master thesis of Jakub Niedermertl, student of Masaryk University Brno.
Under development, let's see where it gets.
As an evening fun, I've added JBoss AS CLI client as a plugin to the JawaBot IRC bot.
That way, more admins may see and discuss the changes they're doing on the fly.
With some security added, it could be useful for someone.
Also I'm going to add IrcAppender so that also logs can be read.
Enjoy :-)
(00:50:50) ozizka: cli connect localhost admin (00:50:51) JawaBot: ozizka: Connected to localhost (00:50:59) ozizka: /system-property=foo:add(value=bar) (00:51:00) JawaBot: ozizka: OK: {"outcome" => "success"} (00:51:13) ozizka: /system-property=foo:read-resource (00:51:13) JawaBot: ozizka: { (00:51:13) JawaBot: ozizka: "outcome" => "success", (00:51:13) JawaBot: ozizka: "result" => {"value" => "bar"} (00:51:14) JawaBot: ozizka: } (00:51:22) ozizka: /system-property=foo:remove(value=bar) (00:51:22) JawaBot: ozizka: OK: {"outcome" => "success"} (00:51:26) ozizka: cli disconnect (00:51:26) gpark: ozizka: Closed.
Source:
package org.jboss.jawabot.plugin.jbossascli.irc; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import org.apache.commons.lang.StringUtils; import org.jboss.as.cli.impl.CLIModelControllerClient; import org.jboss.as.controller.client.ModelControllerClient; import org.jboss.dmr.ModelNode; import org.jboss.jawabot.JawaBot; import org.jboss.jawabot.ex.JawaBotException; import org.jboss.jawabot.ex.JawaBotIOException; import org.jboss.jawabot.irc.IIrcPluginHook; import org.jboss.jawabot.irc.IrcBotProxy; import org.jboss.jawabot.irc.IrcPluginException; import org.jboss.jawabot.irc.IrcPluginHookBase; import org.jboss.jawabot.irc.ent.IrcEvMessage; import org.jboss.loom.utils.as7.AS7CliUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * JBoss AS CLI client over IRC. Could be fun. * * @author Ondrej Zizka */ @ApplicationScoped public class JBossAsCliPlugin extends IrcPluginHookBase implements IIrcPluginHook<Object> { private static final Logger log = LoggerFactory.getLogger( JBossAsCliPlugin.class ); private static final String PLUGIN_NAME = "jboss-as-cli"; private static final Pattern PAT_CLI_CMD = Pattern.compile("((?:/\\w+)*):([\\w-]+(\\(.*\\))?)"); // CLI private ModelControllerClient cli; private Set<String> operators = new HashSet(); @Inject private JawaBot jawaBot; // Just one at a time. Could handle more connections in the future. @Override public void onMessage( IrcEvMessage msg, IrcBotProxy bot ) throws IrcPluginException { String pl = msg.getPayload().trim(); // Connect if( msg.getPayload().startsWith("cli connect ") ){ String[] parts = StringUtils.split( pl ); if( parts.length < 4 ){ bot.sendReplyTo( msg, "Try `cli connect <host> <user> [<port>]`. You'll be asked for password on PM."); } else { try { int port = parts.length > 4 ? Integer.parseInt(parts[4]) : 9999; //ModelControllerClientConfiguration conf = new this.cli = CLIModelControllerClient.Factory.create( parts[2], port ); this.operators.clear(); this.operators.add( msg.getUser() ); bot.sendReplyTo( msg, "Connected to " + parts[2]); } catch( Exception ex ) { bot.sendReplyTo( msg, "Can't connect: " + ex.getMessage() ); } } return; } // Disconnect if( msg.getPayload().startsWith("cli disconnect") ){ if( ! this.operators.contains( msg.getUser() ) ){ this.onlyOpsInfo( msg, bot ); return; } try { this.cli.close(); this.cli = null; this.operators.clear(); bot.sendReplyTo( msg, "Closed." ); } catch( Exception ex ) { bot.sendReplyTo( msg, "Sorry, failed: " + ex.getMessage() ); } return; } // Add operator if( msg.getPayload().startsWith("cli addop ") ){ String[] parts = StringUtils.split( pl ); if( ! this.operators.contains( msg.getUser() ) ){ this.onlyOpsInfo( msg, bot ); return; } try { this.operators.add( parts[2] ); bot.sendReplyTo( msg, "Added." ); } catch( Exception ex ) { bot.sendReplyTo( msg, "Sorry, failed: " + ex.getMessage() ); } this.cli = null; return; } // CLI Command boolean find = PAT_CLI_CMD.matcher(pl).find(); if( find ){ if( ! this.operators.contains( msg.getUser() ) ){ this.onlyOpsInfo( msg, bot ); return; } if( this.cli == null ){ bot.sendReplyTo( msg, "Not connected. Try `cli connect ...`" ); return; } try { ModelNode res = this.cli.execute( AS7CliUtils.parseCommand( pl, true ) ); final String resStr = res.toString(); if( ! resStr.contains("\n") ) bot.sendReplyTo( msg, "OK: " + resStr ); else { for( String string : StringUtils.split( resStr, "\n")) { bot.sendReplyTo( msg, string ); } } } catch( Exception ex ) { bot.sendReplyTo( msg, "Sorry, failed: " + ex.getMessage() ); } return; } }// onMessage() private void onlyOpsInfo( IrcEvMessage msg, IrcBotProxy bot ) { String ops = StringUtils.join( operators, ", "); bot.sendReplyTo( msg, "You can't do that. Ask "+ops+" to add you. `cli addop "+msg.getUser()+"`" ); return; } }// class
I really hate when people repeat something they were taught as a "good thing to do" without thinking about it.
This is true for all aspects of life, starting with right to own a gun over various beliefs to being politically correct.
But there are also plenty of examples in programming.
This will be my list of arguments against illogical statements repeated all over the world by people just because some expert said so 20 years ago in totally different context.
class Foo { public String bar; }
This is not evil. Evil is to make it part of an API. But if you use it within your package, or even a class, it's totally OK. Especially if the classes are not public.
Think of C. How did you model structures in it? struct { ... }. A class with public members is Java's structs.
And if you have an internal algorithm which uses structures, it's nonsense to create a full-blown classes with getters and setters.
If you want to have some examples, check the sources of JDK. Almost any imlementation of complex algorithms uses this. E.g. regular expressions.
Sure, we can argue that JDK isn't always the best Java code. But then, what is.
This nonsense came with the fame of Maven. Lazy programmers didn't separate these in Ant, because it was nearly twice as much XML code.
But Maven made it so easy - all the archetypes had it, IDEs support it, etc. Nice.
But then, some projects intentionally do:
<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*</include> </includes> <excludes> <exclude>**/*.java</exclude> </excludes> <filtering>false</filtering> </resource> <resource> <directory>resources</directory> <includes> <include>**/*</include> </includes> </resource> </resources>
Then someone comes and says, "you know nothing about resources separation, John Snow".
Well, thanks, Mr. Smart, but you're wrong.
Resources separation is not done just because "it's good, mkay?", but for a purpose, which is:
Different people sometimes work on these two, and they are handled by different processes at different time.
But if you have source file, which just happens not to be compiled, then it totally makes sense to have it right next to java files.
Examples of such can be various built-in scripts, Hibernate XML descriptors, log4j.properties, jaxb.properties, default config files, built-in data, etc.
All the kind which is maintained by the very same people which work on the code around. (Unless these files are supposed to be changed by admins etc).
This is especially true for test suites.
Note: Don't confuse with putting these files to the resulting distributed artefact - that's a different story.
Gavin King posted on Google Plus:
Please, please stop saying "prefer composition over inheritance", as if there were no problems best solved using subtyping. And please stop nodding sagely every time you hear someone else say it. Through unthinking repetition, this phase, originally meant as a suggestion to weigh up the advantages and disadvantages of different solutions, has totally lost its original meaning in most people's heads, and has now become a vague, general-purpose item of FUD deployed to make us feel uncomfortable about using one of the basic modeling strategies in our toolbox. There are problems best solved using subtyping (assuming our language has subtypes) and there are problems not best solved using subtyping. But "prefer composition over inheritance" taken literally and without qualification gives us exactly zero advice on how to distinguish the first set of problems from the second. We will only become better programmers when we quit mindlessly repeating vague rules of thumb passed down from the 70's or 80's, and actually start using our own brains to think things through and analyze potential solutions in terms of objective software engineering goals. Grrrr.
File.separator
Happened to me several times that I posted a classloading code, and someone told me "don't use /, that's platform specific". Well, that's true. But only for filesystem paths. For class paths, it's always /. Someone suggested that even for an XPath expression. No comment.
Sometimes, exceptions _can_ be ignored - e.g. if they are misused in a library for flow control.
Comparing objects using just Java heap address is enough in many cases - no need to call hashCode() of both objects. This is typically true for objects which are not stored anywhere during their lifespan. (Not sure if JIT compiler optimizes using equals() on objects without overriden hashCode().)
There are SHA1 and MD5. But who needs such a strong hash for just hashing purposes (not security)? What's the chance that you'll find 2 same contents for which CRC32 will give you same hash?
Scripting languages have the strong advantage in ability to execute code on the fly. That's almost the definition of them.
Sometimes, generating a code is the most effective way to do the job (though not the ellegant). Saying "eval() is evil" blocks you from those solutions.
Javascript's stack can be a tricky beast, especially with closures. "with( ... )" should be avoided in code where you call various lib's code, but in code which is purely yours, "with()" can shorten your code neatly.
Someday in the ancient ages of C or Pascal programming, someone stated that a method should only have one return point. There's no study for that, it's just the matter of taste. Actually, multiple return points can save you a lot of parentheses. If you don't like returns, don't force your taste on others.
Underscore is not a common character to use in Java code, since it's so "C". But sometimes, it makes sense. Especially for internal methods. For exwemple, it can nicely denote which methods do not check the input.
Recursion can be done in few ways. With the increasing popularity of functional languages, some people solve recursive problems by recursive method calls.
Why not. But for simple problems, it's a performance issue. It's enough to put information on a stack. Additional method call consumes more data than just the payload.
That's not all, I often people uncritically accepting anything what reaches their ears, the only filter is "how famous the guy who said it is".
So I have a good source :-)
Suppose you have a big project with tens or hundreds of Maven modules.
Some of them are that kind which only packages things, or intentionally has no tests, or no resources, etc.
Suppose the first case, when you just create a .jar with some resources.
During the build, this is what you would see:
[INFO] ------------------------------------------------------------------------ [INFO] Building JawaBot 2.0 conf/redhat-test 2.0.0.GA-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ JawaBot-config-redhat-test --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 2 resources [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ JawaBot-config-redhat-test --- [INFO] No sources to compile [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ JawaBot-config-redhat-test --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory /home/ondra/work/TOOLS/JawaBot/configs/redhat-test/src/test/resources [INFO] [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ JawaBot-config-redhat-test --- [INFO] No sources to compile [INFO] [INFO] --- maven-surefire-plugin:2.15:test (default-test) @ JawaBot-config-redhat-test --- [INFO] Tests are skipped. [INFO] [INFO] --- maven-jar-plugin:2.2:jar (default-jar) @ JawaBot-config-redhat-test --- [INFO] Building jar: /home/ondra/work/TOOLS/JawaBot/configs/redhat-test/target/JawaBot-config-redhat-test-2.0.0.GA-SNAPSHOT.jar [INFO] [INFO] --- maven-install-plugin:2.3:install (default-install) @ JawaBot-config-redhat-test --- [INFO] Installing /home/ondra/work/TOOLS/JawaBot/configs/redhat-test/target/JawaBot-config-redhat-test-2.0.0.GA-SNAPSHOT.jar to /home/ondra/.m2/repository/org/jboss/jawabot/configs/JawaBot-config-redhat-test/2.0.0.GA-SNAPSHOT/JawaBot-config-redhat-test-2.0.0.GA-SNAPSHOT.jar [INFO] Installing /home/ondra/work/TOOLS/JawaBot/configs/redhat-test/pom.xml to /home/ondra/.m2/repository/org/jboss/jawabot/configs/JawaBot-config-redhat-test/2.0.0.GA-SNAPSHOT/JawaBot-config-redhat-test-2.0.0.GA-SNAPSHOT.pom [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.755s [INFO] Finished at: Sun Jul 07 05:05:54 CEST 2013 [INFO] Final Memory: 10M/213M [INFO] ------------------------------------------------------------------------
But you don't really need all those steps. These executions do nothing:
maven-compiler-plugin:3.1:compile (default-compile) maven-resources-plugin:2.6:testResources (default-testResources) maven-compiler-plugin:3.1:testCompile (default-testCompile) maven-surefire-plugin:2.15:test (default-test)
It's just a second and something (on an SSD disk, though), but that's still worth saving.
Fortunatelly, there's a way to disable these executions.
Notice the identifier in parentheses. That's the execution ID.
If you check so-called super-POM, it has default bindings of the plugin executions for some module type, which you then see even if your pom.xml doesn't define them.
If you re-define them and bind them to a non-existent phase, they won't run:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <executions> <execution> <id>default-test</id> <phase>none</phase> </execution> </executions> </plugin>
The same way, you may disable all the other executions.
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <executions> <execution> <id>default-test</id> <phase>none</phase> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <executions> <execution> <id>default-compile</id> <phase>none</phase> </execution> <execution> <id>default-testCompile</id> <phase>none</phase> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>default-testResources</id> <phase>none</phase> </execution> </executions> </plugin>
[INFO] ------------------------------------------------------------------------ [INFO] Building JawaBot 2.0 conf/redhat-test 2.0.0.GA-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ JawaBot-config-redhat-test --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 2 resources [INFO] [INFO] --- maven-jar-plugin:2.2:jar (default-jar) @ JawaBot-config-redhat-test --- [INFO] Building jar: /home/ondra/work/TOOLS/JawaBot/configs/redhat-test/target/JawaBot-config-redhat-test-2.0.0.GA-SNAPSHOT.jar [INFO] [INFO] --- maven-install-plugin:2.3:install (default-install) @ JawaBot-config-redhat-test --- [INFO] Installing /home/ondra/work/TOOLS/JawaBot/configs/redhat-test/target/JawaBot-config-redhat-test-2.0.0.GA-SNAPSHOT.jar to /home/ondra/.m2/repository/org/jboss/jawabot/configs/JawaBot-config-redhat-test/2.0.0.GA-SNAPSHOT/JawaBot-config-redhat-test-2.0.0.GA-SNAPSHOT.jar [INFO] Installing /home/ondra/work/TOOLS/JawaBot/configs/redhat-test/pom.xml to /home/ondra/.m2/repository/org/jboss/jawabot/configs/JawaBot-config-redhat-test/2.0.0.GA-SNAPSHOT/JawaBot-config-redhat-test-2.0.0.GA-SNAPSHOT.pom [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.181s [INFO] Finished at: Sun Jul 07 05:28:48 CEST 2013 [INFO] Final Memory: 7M/148M [INFO] ------------------------------------------------------------------------
We saved around 30 % of the build time.
Now you probably hate me because it only speeds up modules like this :-) But still even few seconds and smaller logs is worth doing this change.
And that's not all.
Maven's plugin configuration is inherited from parent.
So if you have multiple modules, you may put these suppressing <plugin> elements to some umbrella pom and then just use that as a parent for the modules.
This umbrella may inherit from your project's pom - it won't affect the rest of the project.
As you may see, I don't define plugin versions in my example above; that's because I've set them in <pluginsManagement> in my project's root.
The above technique can also be used to control what artifacts to deploy.
Assume you only want to deploy (upload) a distribution .jar of your application, since the modules are useless for public users.
In this case, you'd disable `default-deploy` (or what the name is) in the root `pom.xml`:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <executions> <execution> <id>default-deploy</id> <phase>none</phase> </execution></executions> </plugin>
And then enable it for submodules:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <executions> <execution> <id>default-deploy</id> <phase>deploy</phase> </execution></executions> </plugin>