View Javadoc
1   package org.argeo.slc.repo;
2   
3   import java.io.BufferedReader;
4   import java.io.InputStreamReader;
5   import java.util.ArrayList;
6   import java.util.Iterator;
7   import java.util.List;
8   import java.util.StringTokenizer;
9   import java.util.jar.JarEntry;
10  import java.util.jar.JarInputStream;
11  import java.util.jar.Manifest;
12  
13  import javax.jcr.Binary;
14  import javax.jcr.Node;
15  import javax.jcr.Property;
16  import javax.jcr.RepositoryException;
17  import javax.jcr.nodetype.NodeType;
18  
19  import org.apache.commons.io.FilenameUtils;
20  import org.apache.commons.io.IOUtils;
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.argeo.jcr.JcrUtils;
24  import org.argeo.slc.CategorizedNameVersion;
25  import org.argeo.slc.DefaultNameVersion;
26  import org.argeo.slc.NameVersion;
27  import org.argeo.slc.SlcException;
28  import org.argeo.slc.SlcNames;
29  import org.argeo.slc.SlcTypes;
30  import org.argeo.slc.build.Distribution;
31  import org.argeo.slc.repo.maven.AetherUtils;
32  import org.eclipse.aether.artifact.Artifact;
33  import org.eclipse.aether.artifact.DefaultArtifact;
34  import org.osgi.framework.Constants;
35  
36  /**
37   * Create or update JCR meta-data for an SLC Modular Distribution
38   * 
39   * Currently, following types are managed: <list>
40   * <li>* .jar: dependency artifacts with csv index</li>
41   * <li>@Deprecated : .pom: artifact (binaries) that indexes a group, the .pom
42   * file contains a tag "dependencyManagement" that list all modules</li> </list>
43   */
44  public class ModularDistributionIndexer implements NodeIndexer, SlcNames {
45  	private final static Log log = LogFactory.getLog(ModularDistributionIndexer.class);
46  
47  	// Constants for csv indexing
48  	private final static String INDEX_FILE_NAME = "modularDistribution.csv";
49  	private String separator = ",";
50  
51  	private Manifest manifest;
52  
53  	public Boolean support(String path) {
54  		if (FilenameUtils.getExtension(path).equals("jar"))
55  			return true;
56  		return false;
57  	}
58  
59  	public void index(Node fileNode) {
60  		Binary fileBinary = null;
61  		try {
62  			String fileNodePath = fileNode.getPath();
63  			if (!support(fileNodePath))
64  				return;
65  
66  			if (!fileNode.isNodeType(NodeType.NT_FILE))
67  				return;
68  
69  			Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
70  			fileBinary = contentNode.getProperty(Property.JCR_DATA).getBinary();
71  
72  			MyModularDistribution currDist = null;
73  			if (FilenameUtils.getExtension(fileNode.getPath()).equals("jar"))
74  				currDist = listModulesFromCsvIndex(fileNode, fileBinary);
75  
76  			if (fileNode.isNodeType(SlcTypes.SLC_MODULAR_DISTRIBUTION) || currDist == null
77  					|| !currDist.nameVersions().hasNext())
78  				return; // already indexed or no modules found
79  			else {
80  				fileNode.addMixin(SlcTypes.SLC_MODULAR_DISTRIBUTION);
81  				fileNode.addMixin(SlcTypes.SLC_CATEGORIZED_NAME_VERSION);
82  				if (currDist.getCategory() != null)
83  					fileNode.setProperty(SLC_CATEGORY, currDist.getCategory());
84  				fileNode.setProperty(SLC_NAME, currDist.getName());
85  				fileNode.setProperty(SLC_VERSION, currDist.getVersion());
86  				indexDistribution(currDist, fileNode);
87  			}
88  
89  			if (log.isTraceEnabled())
90  				log.trace("Indexed " + fileNode + " as modular distribution");
91  		} catch (Exception e) {
92  			throw new SlcException("Cannot list dependencies from " + fileNode, e);
93  		} finally {
94  			JcrUtils.closeQuietly(fileBinary);
95  		}
96  	}
97  
98  	private void indexDistribution(ArgeoOsgiDistribution osgiDist, Node distNode) throws RepositoryException {
99  		distNode.addMixin(SlcTypes.SLC_MODULAR_DISTRIBUTION);
100 		distNode.addMixin(SlcTypes.SLC_CATEGORIZED_NAME_VERSION);
101 		distNode.setProperty(SlcNames.SLC_CATEGORY, osgiDist.getCategory());
102 		distNode.setProperty(SlcNames.SLC_NAME, osgiDist.getName());
103 		distNode.setProperty(SlcNames.SLC_VERSION, osgiDist.getVersion());
104 		if (distNode.hasNode(SLC_MODULES))
105 			distNode.getNode(SLC_MODULES).remove();
106 		Node modules = distNode.addNode(SLC_MODULES, NodeType.NT_UNSTRUCTURED);
107 
108 		for (Iterator<? extends NameVersion> it = osgiDist.nameVersions(); it.hasNext();)
109 			addModule(modules, it.next());
110 	}
111 
112 	// Helpers
113 	private Node addModule(Node modules, NameVersion nameVersion) throws RepositoryException {
114 		CategorizedNameVersion cnv = (CategorizedNameVersion) nameVersion;
115 		Node moduleCoord = null;
116 		moduleCoord = modules.addNode(cnv.getName(), SlcTypes.SLC_MODULE_COORDINATES);
117 		moduleCoord.setProperty(SlcNames.SLC_CATEGORY, cnv.getCategory());
118 		moduleCoord.setProperty(SlcNames.SLC_NAME, cnv.getName());
119 		moduleCoord.setProperty(SlcNames.SLC_VERSION, cnv.getVersion());
120 		return moduleCoord;
121 	}
122 
123 	private MyModularDistribution listModulesFromCsvIndex(Node fileNode, Binary fileBinary) {
124 		JarInputStream jarIn = null;
125 		BufferedReader reader = null;
126 		try {
127 			jarIn = new JarInputStream(fileBinary.getStream());
128 
129 			List<CategorizedNameVersion> modules = new ArrayList<CategorizedNameVersion>();
130 
131 			// meta data
132 			manifest = jarIn.getManifest();
133 			if (manifest == null) {
134 				log.error(fileNode + " has no MANIFEST");
135 				return null;
136 			}
137 			String category = manifest.getMainAttributes().getValue(RepoConstants.SLC_CATEGORY_ID);
138 			String name = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
139 			String version = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
140 
141 			Artifact distribution = new DefaultArtifact(category, name, "jar", version);
142 			// Retrieve the index file
143 			JarEntry indexEntry;
144 			while ((indexEntry = jarIn.getNextJarEntry()) != null) {
145 				String entryName = indexEntry.getName();
146 				if (entryName.equals(INDEX_FILE_NAME)) {
147 					break;
148 				}
149 				try {
150 					jarIn.closeEntry();
151 				} catch (SecurityException se) {
152 					log.error("Invalid signature file digest " + "for Manifest main attributes: " + entryName
153 							+ " while looking for an index in bundle " + name);
154 				}
155 			}
156 			if (indexEntry == null)
157 				return null; // Not a modular definition
158 
159 			if (category == null) {
160 				log.warn("Modular definition found but no " + RepoConstants.SLC_CATEGORY_ID + " in " + fileNode);
161 			}
162 
163 			// Process the index
164 			reader = new BufferedReader(new InputStreamReader(jarIn));
165 			String line = null;
166 			while ((line = reader.readLine()) != null) {
167 				StringTokenizer st = new StringTokenizer(line, separator);
168 				st.nextToken(); // moduleName
169 				st.nextToken(); // moduleVersion
170 				String relativeUrl = st.nextToken();
171 				Artifact currModule = AetherUtils.convertPathToArtifact(relativeUrl, null);
172 				modules.add(new MyCategorizedNameVersion(currModule.getGroupId(), currModule.getArtifactId(),
173 						currModule.getVersion()));
174 			}
175 			return new MyModularDistribution(distribution, modules);
176 		} catch (Exception e) {
177 			throw new SlcException("Cannot list artifacts", e);
178 		} finally {
179 			IOUtils.closeQuietly(jarIn);
180 			IOUtils.closeQuietly(reader);
181 		}
182 	}
183 
184 	/** The created modular distribution */
185 	private static class MyCategorizedNameVersion extends DefaultNameVersion implements CategorizedNameVersion {
186 		private final String category;
187 
188 		public MyCategorizedNameVersion(String category, String name, String version) {
189 			super(name, version);
190 			this.category = category;
191 		}
192 
193 		public String getCategory() {
194 			return category;
195 		}
196 	}
197 
198 	/**
199 	 * A consistent and versioned OSGi distribution, which can be built and tested.
200 	 */
201 	private class MyModularDistribution extends ArtifactDistribution implements ArgeoOsgiDistribution {
202 
203 		private List<CategorizedNameVersion> modules;
204 
205 		public MyModularDistribution(Artifact artifact, List<CategorizedNameVersion> modules) {
206 			super(artifact);
207 			this.modules = modules;
208 		}
209 
210 		public Iterator<CategorizedNameVersion> nameVersions() {
211 			return modules.iterator();
212 		}
213 
214 		// Modular distribution interface methods. Not yet used.
215 		public Distribution getModuleDistribution(String moduleName, String moduleVersion) {
216 			return null;
217 		}
218 
219 		public Object getModulesDescriptor(String descriptorType) {
220 			return null;
221 		}
222 	}
223 
224 	/** Separator used to parse the tabular file, default is "," */
225 	public void setSeparator(String modulesUrlSeparator) {
226 		this.separator = modulesUrlSeparator;
227 	}
228 }