001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License.
018     */
019    
020    package org.apache.geronimo.mavenplugins.car;
021    
022    import java.io.File;
023    import java.io.StringWriter;
024    
025    import java.util.Iterator;
026    import java.util.LinkedHashSet;
027    import java.util.List;
028    import java.util.Properties;
029    
030    import javax.xml.namespace.QName;
031    
032    import org.apache.geronimo.deployment.service.EnvironmentBuilder;
033    import org.apache.geronimo.deployment.xbeans.ArtifactType;
034    import org.apache.geronimo.deployment.xbeans.EnvironmentType;
035    import org.apache.geronimo.kernel.repository.Environment;
036    import org.apache.geronimo.kernel.repository.Artifact;
037    import org.apache.geronimo.kernel.repository.ImportType;
038    
039    import org.apache.maven.model.Dependency;
040    
041    import org.apache.velocity.Template;
042    import org.apache.velocity.VelocityContext;
043    import org.apache.velocity.app.Velocity;
044    import org.apache.velocity.app.VelocityEngine;
045    
046    import org.apache.xmlbeans.XmlCursor;
047    import org.apache.xmlbeans.XmlObject;
048    import org.apache.xmlbeans.XmlOptions;
049    
050    //
051    // TODO: Rename to PreparePlanMojo
052    //
053    
054    /**
055     * Add dependencies to a plan and process with velocity
056     * 
057     * @goal prepare-plan
058     *
059     * @version $Rev: 507395 $ $Date: 2007-02-14 00:36:23 -0500 (Wed, 14 Feb 2007) $
060     */
061    public class PlanProcessorMojo
062        extends AbstractCarMojo
063    {
064        private static final String ENVIRONMENT_LOCAL_NAME = "environment";
065    
066        private static final QName ENVIRONMENT_QNAME = new QName("http://geronimo.apache.org/xml/ns/deployment-1.2", "environment");
067    
068        /**
069         * @parameter expression="${basedir}/src/plan"
070         * @required
071         */
072        protected File sourceDir = null;
073    
074        /**
075         * @parameter expression="${project.build.directory}/plan"
076         * @required
077         */
078        protected File targetDir = null;
079    
080        /**
081         * @parameter default-value="plan.xml"
082         * @required
083         */
084        protected String planFileName = null;
085    
086        /**
087         * @parameter expression="${project.build.directory}/plan/plan.xml"
088         * @required
089         */
090        protected File targetFile = null;
091    
092        private VelocityContext createContext() {
093            VelocityContext context = new VelocityContext();
094    
095            // Load properties, It inherits them all!
096            Properties props = project.getProperties();
097            for (Iterator iter = props.keySet().iterator(); iter.hasNext();) {
098                String key = (String) iter.next();
099                String value = props.getProperty(key);
100    
101                log.debug("Setting " + key + "=" + value);
102                context.put(key, value);
103            }
104    
105            context.put("pom", project);
106    
107            return context;
108        }
109    
110        protected void doExecute() throws Exception {
111            //
112            // FIXME: Do not need velocity here, we only need to filter,
113            //        could use resources plugin to do this for us, or
114            //        implement what resources plugin does here
115            //
116            //        Also velocity does not handle property expansion of expressions like
117            //        ${foo.bar} to the value of the "foo.bar" property :-(
118            //
119            //        Might be better of just hand rolling something...
120            //
121            
122            VelocityContext context = createContext();
123    
124            VelocityEngine velocity = new VelocityEngine();
125            velocity.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, sourceDir.getAbsolutePath());
126            
127            // Don't spit out any logs
128            velocity.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.NullLogSystem");
129            velocity.init();
130    
131            Template template = velocity.getTemplate(planFileName);
132            StringWriter writer = new StringWriter();
133            template.merge(context, writer);
134    
135            String plan = writer.toString();
136    
137            XmlObject doc = XmlObject.Factory.parse(plan);
138            XmlCursor xmlCursor = doc.newCursor();
139            LinkedHashSet dependencies = toDependencies();
140            Artifact configId = new Artifact(project.getGroupId(), project.getArtifactId(), project.getVersion(), "car");
141    
142            try {
143                mergeEnvironment(xmlCursor, configId, dependencies);
144                
145                if (targetDir.exists()) {
146                    if (!targetDir.isDirectory()) {
147                        throw new RuntimeException("TargetDir: " + this.targetDir + " exists and is not a directory");
148                    }
149                }
150                else {
151                    targetDir.mkdirs();
152                }
153    
154                XmlOptions xmlOptions = new XmlOptions();
155                xmlOptions.setSavePrettyPrint();
156                doc.save(targetFile, xmlOptions);
157                
158                log.info("Generated: " + targetFile);
159            }
160            finally {
161                xmlCursor.dispose();
162            }
163        }
164    
165        void mergeEnvironment(final XmlCursor xmlCursor, final Artifact configId, final LinkedHashSet dependencies) {
166            moveToFirstStartElement(xmlCursor);
167    
168            boolean atLeastOneChild = xmlCursor.toFirstChild();
169            if (!atLeastOneChild) {
170                // this is an empty element. Move to EndToken such XmlCursor.beginElement inserts an element inside it.
171                xmlCursor.toEndToken();
172            }
173            QName childName = xmlCursor.getName();
174            Environment oldEnvironment;
175            
176            if (childName != null && childName.getLocalPart().equals(ENVIRONMENT_LOCAL_NAME)) {
177                convertElement(xmlCursor, ENVIRONMENT_QNAME.getNamespaceURI());
178                XmlObject xmlObject = xmlCursor.getObject();
179                EnvironmentType environmentType = (EnvironmentType) xmlObject.copy().changeType(EnvironmentType.type);
180                oldEnvironment = EnvironmentBuilder.buildEnvironment(environmentType);
181                xmlCursor.removeXml();
182            } else {
183                oldEnvironment = new Environment();
184            }
185    
186            Environment newEnvironment = new Environment();
187            newEnvironment.setConfigId(configId);
188            newEnvironment.setDependencies(dependencies);
189    
190            EnvironmentBuilder.mergeEnvironments(oldEnvironment, newEnvironment);
191            EnvironmentType environmentType = EnvironmentBuilder.buildEnvironmentType(oldEnvironment);
192    
193            xmlCursor.beginElement(ENVIRONMENT_QNAME);
194            XmlCursor element = environmentType.newCursor();
195    
196            try {
197                element.copyXmlContents(xmlCursor);
198            }
199            finally {
200                element.dispose();
201            }
202        }
203    
204        private void moveToFirstStartElement(XmlCursor xmlCursor) throws AssertionError {
205            xmlCursor.toStartDoc();
206            xmlCursor.toFirstChild();
207            while (!xmlCursor.currentTokenType().isStart()) {            
208                if (!xmlCursor.toNextSibling()) {
209                    break;
210                }
211            } 
212            if (!xmlCursor.currentTokenType().isStart()) {
213                throw new AssertionError("Cannot find first start element");
214            }
215        }
216    
217        private void convertElement(final XmlCursor cursor, final String namespace) {
218            cursor.push();
219            XmlCursor end = cursor.newCursor();
220    
221            try {
222                end.toCursor(cursor);
223                end.toEndToken();
224    
225                while (cursor.hasNextToken() && cursor.isLeftOf(end)) {
226                    if (cursor.isStart()) {
227                        if (!namespace.equals(cursor.getName().getNamespaceURI())) {
228                            cursor.setName(new QName(namespace, cursor.getName().getLocalPart()));
229                        }
230                    }
231    
232                    cursor.toNextToken();
233                }
234    
235                cursor.pop();
236            }
237            finally {
238                end.dispose();
239            }
240        }
241    
242        private LinkedHashSet toDependencies() {
243            List artifacts = project.getDependencies();
244            LinkedHashSet dependencies = new LinkedHashSet();
245    
246            Iterator iter = artifacts.iterator();
247            while (iter.hasNext()) {
248                Dependency dependency = (Dependency) iter.next();
249    
250                //
251                // HACK: Does not appear that we can get the "extention" status of a dependency,
252                //       so specifically exclude the ones that we know about, like genesis
253                //
254    
255                if (dependency.getGroupId().startsWith("org.apache.geronimo.genesis")) {
256                    continue;
257                }
258    
259                org.apache.geronimo.kernel.repository.Dependency gdep = toGeronimoDependency(dependency);
260                if (gdep != null) {
261                    dependencies.add(gdep);
262                }
263            }
264    
265            return dependencies;
266        }
267    
268        private static org.apache.geronimo.kernel.repository.Dependency toGeronimoDependency(final Dependency dependency) {
269            Artifact artifact = toGeronimoArtifact(dependency);
270            String type = dependency.getType();
271            String scope = dependency.getScope();
272            String groupId = dependency.getGroupId();
273    
274            //!"org.apache.geronimo.specs".equals(groupId) jacc spec needed in plan.xml
275            if ("jar".equalsIgnoreCase(type) && !"junit".equals(groupId) && (scope == null || !scope.equals("provided"))) {
276                if (dependency.getVersion() != null) {
277                    artifact = new Artifact(
278                        artifact.getGroupId(),
279                        artifact.getArtifactId(),
280                        dependency.getVersion(),
281                        artifact.getType());
282                }
283                return new org.apache.geronimo.kernel.repository.Dependency(artifact, ImportType.CLASSES);
284            }
285            else if ("car".equalsIgnoreCase(type) && "runtime".equalsIgnoreCase(scope)) {
286                return new org.apache.geronimo.kernel.repository.Dependency(artifact, ImportType.CLASSES);
287            }
288            //doesn't work
289    //        else if ("car".equalsIgnoreCase(type) && "provided".equalsIgnoreCase(scope)) {
290    //            return new org.apache.geronimo.kernel.repository.Dependency(artifact, ImportType.CLASSES);
291    //        }
292            else if ("car".equalsIgnoreCase(type) && (scope == null || "compile".equalsIgnoreCase(scope))) { //parent
293                return new org.apache.geronimo.kernel.repository.Dependency(artifact, ImportType.ALL);
294            }
295            else {
296                // not one of ours
297                return null;
298            }
299        }
300    
301        private static Artifact toGeronimoArtifact(final Dependency dependency) {
302            String groupId = dependency.getGroupId();
303            String artifactId = dependency.getArtifactId();
304            String version = null;
305            String type = dependency.getType();
306    
307            return new Artifact(groupId, artifactId, version, type);
308        }
309    
310        interface Inserter {
311            ArtifactType insert(EnvironmentType environmentType);
312        }
313    }