001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.controls.runtime.assembly;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.util.ArrayList;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.HashMap;
027: import java.util.Set;
028: import java.util.TreeSet;
029: import java.net.URL;
030: import java.net.URLClassLoader;
031:
032: import org.apache.tools.ant.BuildException;
033: import org.apache.tools.ant.Task;
034: import org.apache.tools.ant.types.Path;
035: import org.apache.tools.ant.types.FileSet;
036: import org.apache.beehive.controls.runtime.generator.apt.ControlClientManifest;
037: import org.apache.beehive.controls.api.assembly.ControlAssemblyException;
038:
039: /**
040: * AssembleTask defines a custom ant task to perform control assembly.
041: * <p>
042: * The core assembly algorithm is documented and implemented in {@link Assembler}.
043: * <p>
044: * Required attributes:<br>
045: * <b>moduleDir</b>: path to the root of J2EE module on which to perform assembly.<br>
046: * <b>srcOutputDir</b>: path to the dir where control assemblers may output source files.
047: * It may be necessary to run additional build steps in order to process such files (for example,
048: * if an assembler outputs Java source code, that code may need to be compiled).<br>
049: * <b>contextFactoryClassname</b>: fully qualified classname of a factory class that implements
050: * {@link org.apache.beehive.controls.api.assembly.ControlAssemblyContext.Factory}. Typically this
051: * would depend on the type of module on which assembly is being run (EJB, webapp, etc). Different
052: * contexts will expose different APIs to control assemblers (making different descriptors available,
053: * etc).
054: * <p>
055: * Supported nested elements:<br>
056: * <b>classpath</b>: specifies the classpath that will be searched for control interfaces/implementations,
057: * control clients and control assemblers.<br>
058: * <b>fileset</b>: specifies the control client manifests that should be processed by this assembly call.<br>
059: * <p>
060: * An example usage of the AssembleTask in an ant build script (build.xml):
061: * <p>
062: <xmp>
063: <taskdef name="assemble" classname="org.apache.beehive.controls.runtime.assembly.AssembleTask"
064: classpathref="controls.dependency.path" onerror="report" />
065:
066: <assemble moduleDir="${build.beans}"
067: srcOutputDir="${build.beansrc}"
068: contextFactoryClassname="org.apache.beehive.controls.runtime.assembly.EJBAssemblyContext$Factory">
069: <classpath>
070: <path refid="test.classpath"/>
071: <pathelement location="${build.beans}"/>
072: </classpath>
073: <fileset dir="${build.beans}">
074: <include name="**\*.controls.properties"/>
075: </fileset>
076: </assemble>
077: </xmp>
078: */
079: public class AssembleTask extends Task {
080: public AssembleTask() {
081: // do nothing
082: }
083:
084: public void setContextFactoryClassName(
085: String contextFactoryClassName) {
086: _contextFactoryClassName = contextFactoryClassName;
087: }
088:
089: public void setModuleDir(File moduleDir) {
090: _moduleDir = moduleDir;
091: }
092:
093: public void setModuleName(String moduleName) {
094: _moduleName = moduleName;
095: }
096:
097: public void setSrcOutputDir(File srcOutputDir) {
098: _srcOutputDir = srcOutputDir;
099: }
100:
101: public void setBindingFile(File bindingFile) {
102: _bindingFile = bindingFile;
103: }
104:
105: public FileSet createFileset() {
106: _clientManifestFileSet = new FileSet();
107: return _clientManifestFileSet;
108: }
109:
110: // used to set classpath as an attribute
111: public void setClasspath(Path classpath) {
112: _classPath = new Path(getProject());
113: _classPath.append(classpath);
114: }
115:
116: // used to set classpath as a nested element
117: public Path createClasspath() {
118: _classPath = new Path(getProject());
119: return _classPath;
120: }
121:
122: public void execute() {
123: validateAttributeSettings();
124:
125: if (_clientManifestFileSet == null) {
126: log("No input fileset specified, nothing to do.");
127: return;
128: }
129:
130: // get list of input files as list of ControlRefs files
131: File filesetDir = _clientManifestFileSet.getDir(getProject());
132: String[] clientManifests = _clientManifestFileSet
133: .getDirectoryScanner(getProject()).getIncludedFiles();
134:
135: if (clientManifests.length == 0) {
136: log("Input fileset contained no files, nothing to do.");
137: return;
138: }
139:
140: List<File> manifestFiles = new ArrayList<File>();
141: for (String mf : clientManifests) {
142: File f = new File(filesetDir, mf);
143: if (!f.exists()) {
144: log("File " + f.getAbsolutePath()
145: + " in input fileset does not exist.");
146: continue;
147: }
148:
149: manifestFiles.add(f);
150: }
151:
152: // REVIEW: nested control usage is handled poorly right now.
153: // Need to refine how we pick up control client manifests, especially
154: // for manifests inside control jars (instead of blindly scanning and
155: // including all manifests inside all jars, should base it on actual nested
156: // control usage as analyzed by starting at non-control clients).
157: try {
158: // Build map of control types to assemble by scanning supplied manifests
159:
160: Map<String, String> controlTypesToImpls = new HashMap<String, String>();
161: Map<String, Set<String>> controlTypesToClients = new HashMap<String, Set<String>>();
162:
163: for (File mf : manifestFiles) {
164: ControlClientManifest ccmf = new ControlClientManifest(
165: mf);
166: String controlClient = ccmf.getControlClient();
167: List<String> controlTypes = ccmf.getControlTypes();
168: for (String ct : controlTypes) {
169: controlTypesToImpls
170: .put(ct, ccmf.getDefaultImpl(ct));
171: Set<String> clients = controlTypesToClients.get(ct);
172: if (clients == null) {
173: clients = new TreeSet<String>();
174: controlTypesToClients.put(ct, clients);
175: }
176: clients.add(controlClient);
177: }
178: }
179:
180: // Build classloader to do loading
181: //
182: // TODO: The module dir should probably be in the classpath, since it seems reasonable
183: // for assemblers to want access to the classes in the module.
184:
185: String[] classpaths = _classPath == null ? new String[0]
186: : _classPath.list();
187: ClassLoader cl = buildClassLoader(classpaths,
188: Assembler.class.getClassLoader());
189:
190: Assembler.assemble(_moduleDir, _moduleName, _srcOutputDir,
191: _contextFactoryClassName, controlTypesToImpls,
192: controlTypesToClients, cl);
193: } catch (Exception e) {
194: e.printStackTrace();
195: throw new BuildException("Assembly failed.", e);
196: }
197: }
198:
199: private void validateAttributeSettings() throws BuildException {
200: if (_contextFactoryClassName == null)
201: throw new BuildException(
202: "The contextFactoryClassName attribute must be set");
203:
204: if (_moduleDir == null)
205: throw new BuildException(
206: "The moduleDir attribute must be set");
207:
208: if (_srcOutputDir == null)
209: throw new BuildException(
210: "The srcOutputDir attribute must be set");
211: }
212:
213: private ClassLoader buildClassLoader(String[] paths,
214: ClassLoader parentCL) throws ControlAssemblyException {
215: List list = new ArrayList();
216: for (int i = 0; i < paths.length; i++) {
217: try {
218: File file = new File(paths[i]);
219: String filePath = file.getCanonicalPath();
220: // ending slash is important for URLs that represent directories
221: if (!filePath.toLowerCase().endsWith(".jar")
222: && !filePath.endsWith("/")) {
223: filePath += "/";
224: }
225: URL url = new URL("file:" + filePath);
226: list.add(url);
227: } catch (IOException e) {
228: throw new ControlAssemblyException(
229: "Unable to include path " + paths[i]
230: + " in classpath. Caught "
231: + e.getClass().getName()
232: + " trying to form this path as a URL.",
233: e);
234: }
235: }
236:
237: URL[] urlArray = new URL[list.size()];
238: urlArray = (URL[]) list.toArray(urlArray);
239:
240: return new URLClassLoader(urlArray, parentCL);
241: }
242:
243: // ant parameter values
244: protected String _contextFactoryClassName;
245: protected File _moduleDir;
246: protected String _moduleName;
247: protected File _srcOutputDir;
248: protected File _bindingFile;
249: protected Path _classPath;
250: protected FileSet _clientManifestFileSet;
251: }
|