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}