blob: 2256f63e8d0b4a856271fcff66f7813711dd85e1 [file] [log] [blame]
Stuart McCullochf3173222012-06-07 21:57:32 +00001package aQute.lib.deployer;
2
3import java.io.*;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00004import java.security.*;
Stuart McCullochf3173222012-06-07 21:57:32 +00005import java.util.*;
6import java.util.jar.*;
7import java.util.regex.*;
8
Stuart McCulloch42151ee2012-07-16 13:43:38 +00009import aQute.bnd.header.*;
10import aQute.bnd.osgi.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000011import aQute.bnd.service.*;
Stuart McCullochcd1ddd72012-07-19 13:11:20 +000012import aQute.bnd.version.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000013import aQute.lib.io.*;
Stuart McCulloch1a890552012-06-29 19:23:09 +000014import aQute.service.reporter.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000015
16public class FileRepo implements Plugin, RepositoryPlugin, Refreshable, RegistryPlugin {
17 public final static String LOCATION = "location";
18 public final static String READONLY = "readonly";
19 public final static String NAME = "name";
20
Stuart McCulloch4482c702012-06-15 13:27:53 +000021 File[] EMPTY_FILES = new File[0];
22 protected File root;
23 Registry registry;
24 boolean canWrite = true;
25 Pattern REPO_FILE = Pattern.compile("([-a-zA-z0-9_\\.]+)-([0-9\\.]+|latest)\\.(jar|lib)");
26 Reporter reporter;
27 boolean dirty;
28 String name;
Stuart McCullochf3173222012-06-07 21:57:32 +000029
Stuart McCulloch4482c702012-06-15 13:27:53 +000030 public FileRepo() {}
Stuart McCullochf3173222012-06-07 21:57:32 +000031
32 public FileRepo(String name, File location, boolean canWrite) {
33 this.name = name;
34 this.root = location;
35 this.canWrite = canWrite;
36 }
37
38 protected void init() throws Exception {
39 // for extensions
40 }
41
Stuart McCulloch4482c702012-06-15 13:27:53 +000042 public void setProperties(Map<String,String> map) {
Stuart McCullochf3173222012-06-07 21:57:32 +000043 String location = map.get(LOCATION);
44 if (location == null)
45 throw new IllegalArgumentException("Location must be set on a FileRepo plugin");
46
47 root = new File(location);
Stuart McCullochf3173222012-06-07 21:57:32 +000048
49 String readonly = map.get(READONLY);
50 if (readonly != null && Boolean.valueOf(readonly).booleanValue())
51 canWrite = false;
52
53 name = map.get(NAME);
54 }
55
56 /**
57 * Get a list of URLs to bundles that are constrained by the bsn and
58 * versionRange.
59 */
Stuart McCulloch669423b2012-06-26 16:34:24 +000060 private File[] get(String bsn, String versionRange) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +000061 init();
62
63 // If the version is set to project, we assume it is not
64 // for us. A project repo will then get it.
65 if (versionRange != null && versionRange.equals("project"))
66 return null;
67
68 //
69 // Check if the entry exists
70 //
71 File f = new File(root, bsn);
72 if (!f.isDirectory())
73 return null;
74
75 //
76 // The version range we are looking for can
77 // be null (for all) or a version range.
78 //
79 VersionRange range;
80 if (versionRange == null || versionRange.equals("latest")) {
81 range = new VersionRange("0");
82 } else
83 range = new VersionRange(versionRange);
84
85 //
86 // Iterator over all the versions for this BSN.
87 // Create a sorted map over the version as key
88 // and the file as URL as value. Only versions
89 // that match the desired range are included in
90 // this list.
91 //
92 File instances[] = f.listFiles();
Stuart McCulloch4482c702012-06-15 13:27:53 +000093 SortedMap<Version,File> versions = new TreeMap<Version,File>();
Stuart McCullochf3173222012-06-07 21:57:32 +000094 for (int i = 0; i < instances.length; i++) {
95 Matcher m = REPO_FILE.matcher(instances[i].getName());
96 if (m.matches() && m.group(1).equals(bsn)) {
97 String versionString = m.group(2);
98 Version version;
99 if (versionString.equals("latest"))
100 version = new Version(Integer.MAX_VALUE);
101 else
102 version = new Version(versionString);
103
104 if (range.includes(version) || versionString.equals(versionRange))
105 versions.put(version, instances[i]);
106 }
107 }
108
109 File[] files = versions.values().toArray(EMPTY_FILES);
110 if ("latest".equals(versionRange) && files.length > 0) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000111 return new File[] {
112 files[files.length - 1]
113 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000114 }
115 return files;
116 }
117
118 public boolean canWrite() {
119 return canWrite;
120 }
121
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000122 protected PutResult putArtifact(File tmpFile, PutOptions options) throws Exception {
123 assert (tmpFile != null);
124 assert (options != null);
Stuart McCullochf3173222012-06-07 21:57:32 +0000125
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000126 Jar jar = null;
127 try {
128 init();
129 dirty = true;
Stuart McCullochf3173222012-06-07 21:57:32 +0000130
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000131 jar = new Jar(tmpFile);
Stuart McCullochf3173222012-06-07 21:57:32 +0000132
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000133 Manifest manifest = jar.getManifest();
134 if (manifest == null)
135 throw new IllegalArgumentException("No manifest in JAR: " + jar);
Stuart McCullochf3173222012-06-07 21:57:32 +0000136
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000137 String bsn = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_SYMBOLICNAME);
138 if (bsn == null)
139 throw new IllegalArgumentException("No Bundle SymbolicName set");
Stuart McCullochf3173222012-06-07 21:57:32 +0000140
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000141 Parameters b = Processor.parseHeader(bsn, null);
142 if (b.size() != 1)
143 throw new IllegalArgumentException("Multiple bsn's specified " + b);
Stuart McCullochf3173222012-06-07 21:57:32 +0000144
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000145 for (String key : b.keySet()) {
146 bsn = key;
147 if (!Verifier.SYMBOLICNAME.matcher(bsn).matches())
148 throw new IllegalArgumentException("Bundle SymbolicName has wrong format: " + bsn);
149 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000150
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000151 String versionString = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_VERSION);
152 Version version;
153 if (versionString == null)
154 version = new Version();
155 else
156 version = new Version(versionString);
Stuart McCullochf3173222012-06-07 21:57:32 +0000157
Stuart McCulloch55fbda52012-08-02 13:26:25 +0000158 if (reporter != null)
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000159 reporter.trace("bsn=%s version=%s", bsn, version);
160
161 File dir = new File(root, bsn);
162 if (!dir.exists() && !dir.mkdirs()) {
163 throw new IOException("Could not create directory " + dir);
164 }
165 String fName = bsn + "-" + version.getWithoutQualifier() + ".jar";
166 File file = new File(dir, fName);
167
168 boolean renamed = false;
169 PutResult result = new PutResult();
170
171 if (reporter != null)
172 reporter.trace("updating %s ", file.getAbsolutePath());
173 if (!file.exists() || file.lastModified() < jar.lastModified()) {
174 if (file.exists()) {
175 IO.delete(file);
176 }
177 IO.rename(tmpFile, file);
178 renamed = true;
179 result.artifact = file.toURI();
180
181 if (reporter != null)
182 reporter.progress(-1, "updated " + file.getAbsolutePath());
183
184 fireBundleAdded(jar, file);
185 } else {
186 if (reporter != null) {
187 reporter.progress(-1, "Did not update " + jar + " because repo has a newer version");
188 reporter.trace("NOT Updating " + fName + " (repo is newer)");
189 }
190 }
191
192 File latest = new File(dir, bsn + "-latest.jar");
193 boolean latestExists = latest.exists() && latest.isFile();
194 boolean latestIsOlder = latestExists && (latest.lastModified() < jar.lastModified());
195 if ((options.createLatest && !latestExists) || latestIsOlder) {
196 if (latestExists) {
197 IO.delete(latest);
198 }
199 if (!renamed) {
200 IO.rename(tmpFile, latest);
201 } else {
202 IO.copy(file, latest);
203 }
204 result.latest = latest.toURI();
205 }
206
207 return result;
208 }
209 finally {
210 if (jar != null) {
211 jar.close();
Stuart McCulloch55fbda52012-08-02 13:26:25 +0000212 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000213 }
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000214 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000215
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000216 /* a straight copy of this method lives in LocalIndexedRepo */
217 public PutResult put(InputStream stream, PutOptions options) throws Exception {
218 /* both parameters are required */
219 if ((stream == null) || (options == null)) {
220 throw new IllegalArgumentException("No stream and/or options specified");
Stuart McCullochf3173222012-06-07 21:57:32 +0000221 }
222
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000223 /* determine if the put is allowed */
224 if (!canWrite) {
225 throw new IOException("Repository is read-only");
226 }
227
228 /* the root directory of the repository has to be a directory */
229 if (!root.isDirectory()) {
230 throw new IOException("Repository directory " + root + " is not a directory");
231 }
232
233 /* determine if the artifact needs to be verified */
234 boolean verifyFetch = (options.digest != null);
235 boolean verifyPut = !options.allowArtifactChange;
236
237 /* determine which digests are needed */
238 boolean needFetchDigest = verifyFetch || verifyPut;
239 boolean needPutDigest = verifyPut || options.generateDigest;
240
241 /*
242 * setup a new stream that encapsulates the stream and calculates (when
243 * needed) the digest
244 */
245 DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("SHA-1"));
246 dis.on(needFetchDigest);
247
248 File tmpFile = null;
249 try {
250 /*
251 * copy the artifact from the (new/digest) stream into a temporary
252 * file in the root directory of the repository
253 */
254 tmpFile = IO.createTempFile(root, "put", ".bnd");
255 IO.copy(dis, tmpFile);
256
257 /* get the digest if available */
258 byte[] disDigest = needFetchDigest ? dis.getMessageDigest().digest() : null;
259
260 /* verify the digest when requested */
261 if (verifyFetch && !MessageDigest.isEqual(options.digest, disDigest)) {
262 throw new IOException("Retrieved artifact digest doesn't match specified digest");
263 }
264
265 /* put the artifact into the repository (from the temporary file) */
266 PutResult r = putArtifact(tmpFile, options);
267
268 /* calculate the digest when requested */
269 if (needPutDigest && (r.artifact != null)) {
270 MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
271 IO.copy(new File(r.artifact), sha1);
272 r.digest = sha1.digest();
273 }
274
275 /* verify the artifact when requested */
276 if (verifyPut && (r.digest != null) && !MessageDigest.isEqual(disDigest, r.digest)) {
277 File f = new File(r.artifact);
278 if (f.exists()) {
279 IO.delete(f);
280 }
281 throw new IOException("Stored artifact digest doesn't match specified digest");
282 }
283
284 return r;
285 }
286 finally {
287 if (tmpFile != null && tmpFile.exists()) {
288 IO.delete(tmpFile);
289 }
290 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000291 }
292
293 protected void fireBundleAdded(Jar jar, File file) {
294 if (registry == null)
295 return;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000296 List<RepositoryListenerPlugin> listeners = registry.getPlugins(RepositoryListenerPlugin.class);
Stuart McCullochf3173222012-06-07 21:57:32 +0000297 for (RepositoryListenerPlugin listener : listeners) {
298 try {
299 listener.bundleAdded(this, jar, file);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000300 }
301 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000302 if (reporter != null)
303 reporter.warning("Repository listener threw an unexpected exception: %s", e);
304 }
305 }
306 }
307
308 public void setLocation(String string) {
309 root = new File(string);
310 if (!root.isDirectory())
311 throw new IllegalArgumentException("Invalid repository directory");
312 }
313
314 public void setReporter(Reporter reporter) {
315 this.reporter = reporter;
316 }
317
318 public List<String> list(String regex) throws Exception {
319 init();
320 Instruction pattern = null;
321 if (regex != null)
322 pattern = new Instruction(regex);
323
324 List<String> result = new ArrayList<String>();
325 if (root == null) {
326 if (reporter != null)
327 reporter.error("FileRepo root directory is not set.");
328 } else {
329 File[] list = root.listFiles();
330 if (list != null) {
331 for (File f : list) {
332 if (!f.isDirectory())
333 continue; // ignore non-directories
334 String fileName = f.getName();
335 if (fileName.charAt(0) == '.')
336 continue; // ignore hidden files
337 if (pattern == null || pattern.matches(fileName))
338 result.add(fileName);
339 }
340 } else if (reporter != null)
341 reporter.error("FileRepo root directory (%s) does not exist", root);
342 }
343
344 return result;
345 }
346
347 public List<Version> versions(String bsn) throws Exception {
348 init();
349 File dir = new File(root, bsn);
350 if (dir.isDirectory()) {
351 String versions[] = dir.list();
352 List<Version> list = new ArrayList<Version>();
353 for (String v : versions) {
354 Matcher m = REPO_FILE.matcher(v);
355 if (m.matches()) {
356 String version = m.group(2);
357 if (version.equals("latest"))
358 version = Integer.MAX_VALUE + "";
359 list.add(new Version(version));
360 }
361 }
362 return list;
363 }
364 return null;
365 }
366
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000367 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000368 public String toString() {
369 return String.format("%-40s r/w=%s", root.getAbsolutePath(), canWrite());
370 }
371
372 public File getRoot() {
373 return root;
374 }
375
376 public boolean refresh() {
377 if (dirty) {
378 dirty = false;
379 return true;
Stuart McCulloch669423b2012-06-26 16:34:24 +0000380 }
381 return false;
Stuart McCullochf3173222012-06-07 21:57:32 +0000382 }
383
384 public String getName() {
385 if (name == null) {
386 return toString();
387 }
388 return name;
389 }
390
391 public Jar get(String bsn, Version v) throws Exception {
392 init();
393 File bsns = new File(root, bsn);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000394 File version = new File(bsns, bsn + "-" + v.getMajor() + "." + v.getMinor() + "." + v.getMicro() + ".jar");
395 if (version.exists())
Stuart McCullochf3173222012-06-07 21:57:32 +0000396 return new Jar(version);
Stuart McCulloch669423b2012-06-26 16:34:24 +0000397 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +0000398 }
399
Stuart McCulloch4482c702012-06-15 13:27:53 +0000400 public File get(String bsn, String version, Strategy strategy, Map<String,String> properties) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000401 if (version == null)
402 version = "0.0.0";
403
404 if (strategy == Strategy.EXACT) {
405 VersionRange vr = new VersionRange(version);
406 if (vr.isRange())
407 return null;
408
409 if (vr.getHigh().getMajor() == Integer.MAX_VALUE)
410 version = "latest";
411
412 File file = IO.getFile(root, bsn + "/" + bsn + "-" + version + ".jar");
413 if (file.isFile())
414 return file;
Stuart McCulloch669423b2012-06-26 16:34:24 +0000415 file = IO.getFile(root, bsn + "/" + bsn + "-" + version + ".lib");
416 if (file.isFile())
417 return file;
Stuart McCullochf3173222012-06-07 21:57:32 +0000418 return null;
419
420 }
421 File[] files = get(bsn, version);
422 if (files == null || files.length == 0)
423 return null;
424
425 if (files.length >= 0) {
426 switch (strategy) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000427 case LOWEST :
428 return files[0];
429 case HIGHEST :
430 return files[files.length - 1];
Stuart McCulloch1b98aa02012-06-18 11:15:15 +0000431 case EXACT :
432 // TODO
433 break;
Stuart McCullochf3173222012-06-07 21:57:32 +0000434 }
435 }
436 return null;
437 }
438
439 public void setRegistry(Registry registry) {
440 this.registry = registry;
441 }
442
443 public String getLocation() {
444 return root.toString();
445 }
446
447}