001/** 002 * Copyright 2011-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.maven.plugins.guice; 017 018import static com.google.common.base.Optional.absent; 019import static com.google.common.base.Preconditions.checkState; 020import static com.google.common.collect.Lists.newArrayList; 021import static com.google.inject.Guice.createInjector; 022import static java.lang.Character.toUpperCase; 023import static org.apache.commons.lang3.StringUtils.isNotBlank; 024import static org.kuali.common.jute.base.Exceptions.illegalArgument; 025import static org.kuali.common.jute.base.Optionals.fromTrimToNull; 026 027import java.lang.annotation.Annotation; 028import java.util.List; 029 030import org.apache.maven.plugin.AbstractMojo; 031import org.apache.maven.plugins.annotations.Component; 032import org.apache.maven.plugins.annotations.Execute; 033import org.apache.maven.plugins.annotations.Mojo; 034import org.apache.maven.plugins.annotations.Parameter; 035import org.apache.maven.project.MavenProject; 036import org.apache.maven.settings.Settings; 037 038import com.google.common.base.Optional; 039import com.google.inject.AbstractModule; 040import com.google.inject.Injector; 041import com.google.inject.Key; 042 043/** 044 * <p> 045 * Create a Guice injector from the configured modules, get a {@code java.lang.Runnable} from the injector, and run it. 046 * </p> 047 * 048 * <p> 049 * If no explicit modules are configured, look for a default module using {@code groupId + artifactId} by converting {@code artifactId} to camel case and appending the word 050 * {@code Module}. For example, the default module that would be loaded using this plugin's GAV information is: 051 * 052 * <pre> 053 * org.kuali.maven.plugins.GuiceMavenPluginModule 054 * </pre> 055 * 056 * </p> 057 */ 058@Mojo(name = GuiceMojo.RUN, threadSafe = true) 059@Execute(goal = GuiceMojo.RUN) 060public class GuiceMojo extends AbstractMojo { 061 062 static final String RUN = "run"; 063 private static final String MODULE = "Module"; 064 065 @Component 066 MavenProject project; 067 068 @Component 069 Settings settings; 070 071 /** 072 * List of Guice modules to use when creating the injector. If supplied, these come before {@code guice.module} 073 */ 074 @Parameter(property = "guice.modules") 075 List<String> modules = newArrayList(); 076 077 /** 078 * The Guice module to use when creating the injector. This comes after {@code guice.modules} (if any were supplied). 079 */ 080 @Parameter(property = "guice.module") 081 String module; 082 083 /** 084 * If supplied, the {@code java.lang.Runnable} must be injected using this annotation 085 */ 086 @Parameter(property = "guice.annotation") 087 String annotation; 088 089 @Override 090 public void execute() { 091 // TODO Gain a better understanding of Guice scopes and if a custom scope might be appropriate here 092 // TODO Strongly suspect what is being done here would be considered naive, and better accomplished via some kind 093 // TODO of "project scope", or "reactor scope" or something similar, possibly not even being done as a plugin 094 // TODO https://github.com/google/guice/wiki/CustomScopes 095 Optional<AbstractModule> module = getModule(this); 096 checkState(module.isPresent() || !getModules().isEmpty(), "no modules"); 097 List<AbstractModule> modules = getModules(module, getModules(), new MavenModule(this)); 098 Injector injector = createInjector(modules); 099 Runnable runnable = getRunnable(injector, fromTrimToNull(annotation)); 100 runnable.run(); 101 } 102 103 protected Optional<AbstractModule> getModule(GuiceMojo mojo) { 104 if (isNotBlank(mojo.getModule())) { 105 // they've supplied a specific module, use it 106 AbstractModule module = getModule(mojo.getModule()); 107 return Optional.of(module); 108 } else { 109 // otherwise see if we can find a default module 110 return getDefaultModule(mojo.getProject()); 111 } 112 } 113 114 private List<AbstractModule> getModules(Optional<AbstractModule> module, List<String> modules, MavenModule maven) { 115 List<AbstractModule> list = newArrayList(); 116 list.add(maven); 117 for (String moduleName : modules) { 118 AbstractModule element = getModule(moduleName); 119 list.add(element); 120 } 121 if (module.isPresent()) { 122 list.add(module.get()); 123 } 124 return list; 125 } 126 127 private Runnable getRunnable(Injector injector, Optional<String> annotation) { 128 if (annotation.isPresent()) { 129 Class<? extends Annotation> type = newClass(annotation.get()); 130 Key<Runnable> key = Key.get(Runnable.class, type); 131 return injector.getInstance(key); 132 } else { 133 return injector.getInstance(Runnable.class); 134 } 135 } 136 137 private Optional<AbstractModule> getDefaultModule(MavenProject project) { 138 String className = getDefaultModuleClassName(project); 139 try { 140 AbstractModule module = getModule(className); 141 return Optional.of(module); 142 } catch (IllegalArgumentException e) { 143 // if we can't lookup a default module, just return absent 144 return absent(); 145 } 146 } 147 148 private String getDefaultModuleClassName(MavenProject project) { 149 String groupId = project.getGroupId(); 150 String artifactId = project.getArtifactId(); 151 return groupId + "." + capitalize(artifactId) + MODULE; 152 } 153 154 protected String capitalize(String artifactId) { 155 char[] chars = artifactId.replace('_', '-').toCharArray(); 156 StringBuilder sb = new StringBuilder(); 157 char prevChar = 0; 158 for (char c : chars) { 159 if (c == '-') { 160 // skip it, don't add it to the string 161 } else if (prevChar == 0 || prevChar == '-') { 162 // if it's the first character OR the previous character was a dash 163 // convert the current character to upper case and append it 164 sb.append(toUpperCase(c)); 165 } else { 166 // otherwise just append the current character 167 sb.append(c); 168 } 169 prevChar = c; 170 } 171 return sb.toString(); 172 } 173 174 protected AbstractModule getModule(String module) { 175 Class<? extends AbstractModule> type = newClass(module); 176 return newInstance(type); 177 } 178 179 @SuppressWarnings("unchecked") 180 protected <T> T newClass(String type) { 181 try { 182 return (T) Class.forName(type); 183 } catch (Exception e) { 184 throw illegalArgument(e); 185 } 186 } 187 188 protected <T> T newInstance(Class<T> type) { 189 try { 190 return type.newInstance(); 191 } catch (Exception e) { 192 throw illegalArgument(e); 193 } 194 } 195 196 public String getModule() { 197 return module; 198 } 199 200 public void setModule(String module) { 201 this.module = module; 202 } 203 204 public MavenProject getProject() { 205 return project; 206 } 207 208 public Settings getSettings() { 209 return settings; 210 } 211 212 public void setModules(List<String> modules) { 213 this.modules = modules; 214 } 215 216 public List<String> getModules() { 217 return modules; 218 } 219 220 public String getAnnotation() { 221 return annotation; 222 } 223 224 public void setAnnotation(String annotation) { 225 this.annotation = annotation; 226 } 227 228}