blob: 7fa7bb3b376cc98eaceb72fe1360612459f5aa62 [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.*;
Stuart McCullochf3173222012-06-07 21:57:32 +00006import java.util.regex.*;
7
Stuart McCulloch42151ee2012-07-16 13:43:38 +00008import aQute.bnd.osgi.*;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00009import aQute.bnd.osgi.Verifier;
Stuart McCullochf3173222012-06-07 21:57:32 +000010import aQute.bnd.service.*;
Stuart McCullochcd1ddd72012-07-19 13:11:20 +000011import aQute.bnd.version.*;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000012import aQute.lib.collections.*;
13import aQute.lib.hex.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000014import aQute.lib.io.*;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000015import aQute.libg.command.*;
16import aQute.libg.cryptography.*;
17import aQute.libg.reporter.*;
Stuart McCulloch1a890552012-06-29 19:23:09 +000018import aQute.service.reporter.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000019
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000020/**
21 * A FileRepo is the primary and example implementation of a repository based on
22 * a file system. It maintains its files in a bsn/bsn-version.jar style from a
23 * given location. It implements all the functions of the
24 * {@link RepositoryPlugin}, {@link Refreshable}, {@link Actionable}, and
25 * {@link Closeable}. The FileRepo can be extended or used as is. When used as
26 * is, it is possible to add shell commands to the life cycle of the FileRepo.
27 * This life cycle is as follows:
28 * <ul>
29 * <li>{@link #CMD_INIT} - Is only executed when the location did not exist</li>
30 * <li>{@link #CMD_OPEN} - Called (after init if necessary) to open it once</li>
31 * <li>{@link #CMD_REFRESH} - Called when refreshed.</li>
32 * <li>{@link #CMD_BEFORE_PUT} - Before the file system is changed</li>
33 * <li>{@link #CMD_AFTER_PUT} - After the file system has changed, and the put
34 * <li>{@link #CMD_BEFORE_GET} - Before the file is gotten</li>
35 * <li>{@link #CMD_AFTER_ACTION} - Before the file is gotten</li>
36 * <li>{@link #CMD_CLOSE} - When the repo is closed and no more actions will
37 * take place</li> was a success</li>
38 * <li>{@link #CMD_ABORT_PUT} - When the put is aborted.</li>
39 * <li>{@link #CMD_CLOSE} - To close the repository.</li>
40 * </ul>
41 * Additionally, it is possible to set the {@link #CMD_SHELL} and the
42 * {@link #CMD_PATH}. Notice that you can use the ${global} macro to read global
43 * (that is, machine local) settings from the ~/.bnd/settings.json file (can be
44 * managed with bnd).
45 */
46public class FileRepo implements Plugin, RepositoryPlugin, Refreshable, RegistryPlugin, Actionable, Closeable {
Stuart McCullochf3173222012-06-07 21:57:32 +000047
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000048 /**
49 * If set, will trace to stdout. Works only if no reporter is set.
50 */
51 public final static String TRACE = "trace";
52
53 /**
54 * Property name for the location of the repo, must be a valid path name
55 * using forward slashes (see {@link IO#getFile(String)}.
56 */
57 public final static String LOCATION = "location";
58
59 /**
60 * Property name for the readonly state of the repository. If no, will
61 * read/write, otherwise it must be a boolean value read by
62 * {@link Boolean#parseBoolean(String)}. Read only repositories will not
63 * accept writes.
64 */
65 public final static String READONLY = "readonly";
66
67 /**
68 * Set the name of this repository (optional)
69 */
70 public final static String NAME = "name";
71
72 /**
73 * Path property for commands. A comma separated path for directories to be
74 * searched for command. May contain $ @} which will be replaced by the
75 * system path. If this property is not set, the system path is assumed.
76 */
77 public static final String CMD_PATH = "cmd.path";
78
79 /**
80 * The name ( and path) of the shell to execute the commands. By default
81 * this is sh and searched in the path.
82 */
83 public static final String CMD_SHELL = "cmd.shell";
84
85 /**
86 * Property for commands. The command only runs when the location does not
87 * exist. </p>
88 *
89 * @param rootFile
90 * the root of the repo (directory exists)
91 */
92 public static final String CMD_INIT = "cmd.init";
93
94 /**
95 * Property for commands. Command is run before the repo is first used. </p>
96 *
97 * @param $0
98 * rootFile the root of the repo (directory exists)
99 */
100 public static final String CMD_OPEN = "cmd.open";
101
102 /**
103 * Property for commands. The command runs after a put operation. </p>
104 *
105 * @param $0
106 * the root of the repo (directory exists)
107 * @param $1
108 * the file that was put
109 * @param $2
110 * the hex checksum of the file
111 */
112 public static final String CMD_AFTER_PUT = "cmd.after.put";
113
114 /**
115 * Property for commands. The command runs when the repository is refreshed.
116 * </p>
117 *
118 * @param $
119 * {0} the root of the repo (directory exists)
120 */
121 public static final String CMD_REFRESH = "cmd.refresh";
122
123 /**
124 * Property for commands. The command runs after the file is put. </p>
125 *
126 * @param $0
127 * the root of the repo (directory exists)
128 * @param $1
129 * the path to a temporary file
130 */
131 public static final String CMD_BEFORE_PUT = "cmd.before.put";
132
133 /**
134 * Property for commands. The command runs when a put is aborted after file
135 * changes were made. </p>
136 *
137 * @param $0
138 * the root of the repo (directory exists)
139 * @param $1
140 * the temporary file that was used (optional)
141 */
142 public static final String CMD_ABORT_PUT = "cmd.abort.put";
143
144 /**
145 * Property for commands. The command runs after the file is put. </p>
146 *
147 * @param $0
148 * the root of the repo (directory exists)
149 */
150 public static final String CMD_CLOSE = "cmd.close";
151
152 /**
153 * Property for commands. Will be run after an action has been executed.
154 * </p>
155 *
156 * @param $0
157 * the root of the repo (directory exists)
158 * @param $1
159 * the path to the file that the action was executed on
160 * @param $2
161 * the action executed
162 */
163 public static final String CMD_AFTER_ACTION = "cmd.after.action";
164
165 /**
166 * Called before a before get.
167 *
168 * @param $0
169 * the root of the repo (directory exists)
170 * @param $1
171 * the bsn
172 * @param $2
173 * the version
174 */
175 public static final String CMD_BEFORE_GET = "cmd.before.get";
176
177 /**
178 * Options used when the options are null
179 */
180 static final PutOptions DEFAULTOPTIONS = new PutOptions();
181
182 String shell;
183 String path;
184 String init;
185 String open;
186 String refresh;
187 String beforePut;
188 String afterPut;
189 String abortPut;
190 String beforeGet;
191 String close;
192 String action;
193
194 File[] EMPTY_FILES = new File[0];
Stuart McCulloch4482c702012-06-15 13:27:53 +0000195 protected File root;
196 Registry registry;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000197 boolean canWrite = true;
198 Pattern REPO_FILE = Pattern.compile("([-a-zA-z0-9_\\.]+)-([0-9\\.]+)\\.(jar|lib)");
Stuart McCulloch4482c702012-06-15 13:27:53 +0000199 Reporter reporter;
200 boolean dirty;
201 String name;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000202 boolean inited;
203 boolean trace;
Stuart McCullochf3173222012-06-07 21:57:32 +0000204
Stuart McCulloch4482c702012-06-15 13:27:53 +0000205 public FileRepo() {}
Stuart McCullochf3173222012-06-07 21:57:32 +0000206
207 public FileRepo(String name, File location, boolean canWrite) {
208 this.name = name;
209 this.root = location;
210 this.canWrite = canWrite;
211 }
212
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000213 /**
214 * Initialize the repository Subclasses should first call this method and
215 * then if it returns true, do their own initialization
216 *
217 * @return true if initialized, false if already had been initialized.
218 * @throws Exception
219 */
220 protected boolean init() throws Exception {
221 if (inited)
222 return false;
223
224 inited = true;
225
226 if (reporter == null) {
227 ReporterAdapter reporter = trace ? new ReporterAdapter(System.out) : new ReporterAdapter();
228 reporter.setTrace(trace);
229 reporter.setExceptions(trace);
230 this.reporter = reporter;
231 }
232
233 if (!root.isDirectory()) {
234 root.mkdirs();
235 if (!root.isDirectory())
236 throw new IllegalArgumentException("Location cannot be turned into a directory " + root);
237
238 exec(init, root.getAbsolutePath());
239 }
240 open();
241 return true;
Stuart McCullochf3173222012-06-07 21:57:32 +0000242 }
243
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000244 /**
245 * @see aQute.bnd.service.Plugin#setProperties(java.util.Map)
246 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000247 public void setProperties(Map<String,String> map) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000248 String location = map.get(LOCATION);
249 if (location == null)
250 throw new IllegalArgumentException("Location must be set on a FileRepo plugin");
251
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000252 root = IO.getFile(IO.home, location);
Stuart McCullochf3173222012-06-07 21:57:32 +0000253 String readonly = map.get(READONLY);
254 if (readonly != null && Boolean.valueOf(readonly).booleanValue())
255 canWrite = false;
256
257 name = map.get(NAME);
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000258 path = map.get(CMD_PATH);
259 shell = map.get(CMD_SHELL);
260 init = map.get(CMD_INIT);
261 open = map.get(CMD_OPEN);
262 refresh = map.get(CMD_REFRESH);
263 beforePut = map.get(CMD_BEFORE_PUT);
264 abortPut = map.get(CMD_ABORT_PUT);
265 afterPut = map.get(CMD_AFTER_PUT);
266 beforeGet = map.get(CMD_BEFORE_GET);
267 close = map.get(CMD_CLOSE);
268 action = map.get(CMD_AFTER_ACTION);
269
270 trace = map.get(TRACE) != null && Boolean.parseBoolean(map.get(TRACE));
Stuart McCullochf3173222012-06-07 21:57:32 +0000271 }
272
273 /**
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000274 * Answer if this repository can write.
Stuart McCullochf3173222012-06-07 21:57:32 +0000275 */
Stuart McCullochf3173222012-06-07 21:57:32 +0000276 public boolean canWrite() {
277 return canWrite;
278 }
279
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000280 /**
281 * Local helper method that tries to insert a file in the repository. This
282 * method can be overridden but MUST not change the content of the tmpFile.
283 * This method should also create a latest version of the artifact for
284 * reference by tools like ant etc. </p> It is allowed to rename the file,
285 * the tmp file must be beneath the root directory to prevent rename
286 * problems.
287 *
288 * @param tmpFile
289 * source file
290 * @param digest
291 * @return a File that contains the content of the tmpFile
292 * @throws Exception
293 */
294 protected File putArtifact(File tmpFile, byte[] digest) throws Exception {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000295 assert (tmpFile != null);
Stuart McCullochf3173222012-06-07 21:57:32 +0000296
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000297 Jar jar = new Jar(tmpFile);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000298 try {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000299 dirty = true;
Stuart McCullochf3173222012-06-07 21:57:32 +0000300
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000301 String bsn = jar.getBsn();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000302 if (bsn == null)
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000303 throw new IllegalArgumentException("No bsn set in jar: " + tmpFile);
Stuart McCullochf3173222012-06-07 21:57:32 +0000304
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000305 String versionString = jar.getVersion();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000306 if (versionString == null)
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000307 versionString = "0";
308 else if (!Verifier.isVersion(versionString))
309 throw new IllegalArgumentException("Incorrect version in : " + tmpFile + " " + versionString);
Stuart McCullochf3173222012-06-07 21:57:32 +0000310
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000311 Version version = new Version(versionString);
312
313 reporter.trace("bsn=%s version=%s", bsn, version);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000314
315 File dir = new File(root, bsn);
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000316 dir.mkdirs();
317 if (!dir.isDirectory())
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000318 throw new IOException("Could not create directory " + dir);
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000319
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000320 String fName = bsn + "-" + version.getWithoutQualifier() + ".jar";
321 File file = new File(dir, fName);
322
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000323 reporter.trace("updating %s ", file.getAbsolutePath());
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000324
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000325 IO.rename(tmpFile, file);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000326
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000327 fireBundleAdded(jar, file);
328 afterPut(file, bsn, version, Hex.toHexString(digest));
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000329
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000330 // TODO like to beforeGet rid of the latest option. This is only
331 // used to have a constant name for the outside users (like ant)
332 // we should be able to handle this differently?
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000333 File latest = new File(dir, bsn + "-latest.jar");
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000334 IO.copy(file, latest);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000335
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000336 reporter.trace("updated %s", file.getAbsolutePath());
337
338 return file;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000339 }
340 finally {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000341 jar.close();
Stuart McCullochf3173222012-06-07 21:57:32 +0000342 }
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000343 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000344
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000345 /*
346 * (non-Javadoc)
347 * @see aQute.bnd.service.RepositoryPlugin#put(java.io.InputStream,
348 * aQute.bnd.service.RepositoryPlugin.PutOptions)
349 */
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000350 public PutResult put(InputStream stream, PutOptions options) throws Exception {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000351 /* determine if the put is allowed */
352 if (!canWrite) {
353 throw new IOException("Repository is read-only");
354 }
355
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000356 assert stream != null;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000357
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000358 if (options == null)
359 options = DEFAULTOPTIONS;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000360
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000361 init();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000362
363 /*
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000364 * copy the artifact from the (new/digest) stream into a temporary file
365 * in the root directory of the repository
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000366 */
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000367 File tmpFile = IO.createTempFile(root, "put", ".jar");
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000368 try {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000369 DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("SHA-1"));
370 try {
371 IO.copy(dis, tmpFile);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000372
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000373 byte[] digest = dis.getMessageDigest().digest();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000374
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000375 if (options.digest != null && !Arrays.equals(digest, options.digest))
376 throw new IOException("Retrieved artifact digest doesn't match specified digest");
377
378 /*
379 * put the artifact into the repository (from the temporary
380 * file)
381 */
382 beforePut(tmpFile);
383 File file = putArtifact(tmpFile, digest);
384 file.setReadOnly();
385
386 PutResult result = new PutResult();
387 result.digest = digest;
388 result.artifact = file.toURI();
389
390 return result;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000391 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000392 finally {
393 dis.close();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000394 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000395 }
396 catch (Exception e) {
397 abortPut(tmpFile);
398 throw e;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000399 }
400 finally {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000401 IO.delete(tmpFile);
Stuart McCullochf3173222012-06-07 21:57:32 +0000402 }
403 }
404
405 public void setLocation(String string) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000406 root = IO.getFile(string);
Stuart McCullochf3173222012-06-07 21:57:32 +0000407 }
408
409 public void setReporter(Reporter reporter) {
410 this.reporter = reporter;
411 }
412
413 public List<String> list(String regex) throws Exception {
414 init();
415 Instruction pattern = null;
416 if (regex != null)
417 pattern = new Instruction(regex);
418
419 List<String> result = new ArrayList<String>();
420 if (root == null) {
421 if (reporter != null)
422 reporter.error("FileRepo root directory is not set.");
423 } else {
424 File[] list = root.listFiles();
425 if (list != null) {
426 for (File f : list) {
427 if (!f.isDirectory())
428 continue; // ignore non-directories
429 String fileName = f.getName();
430 if (fileName.charAt(0) == '.')
431 continue; // ignore hidden files
432 if (pattern == null || pattern.matches(fileName))
433 result.add(fileName);
434 }
435 } else if (reporter != null)
436 reporter.error("FileRepo root directory (%s) does not exist", root);
437 }
438
439 return result;
440 }
441
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000442 public SortedSet<Version> versions(String bsn) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000443 init();
444 File dir = new File(root, bsn);
445 if (dir.isDirectory()) {
446 String versions[] = dir.list();
447 List<Version> list = new ArrayList<Version>();
448 for (String v : versions) {
449 Matcher m = REPO_FILE.matcher(v);
450 if (m.matches()) {
451 String version = m.group(2);
452 if (version.equals("latest"))
453 version = Integer.MAX_VALUE + "";
454 list.add(new Version(version));
455 }
456 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000457 return new SortedList<Version>(list);
Stuart McCullochf3173222012-06-07 21:57:32 +0000458 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000459 return SortedList.empty();
Stuart McCullochf3173222012-06-07 21:57:32 +0000460 }
461
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000462 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000463 public String toString() {
464 return String.format("%-40s r/w=%s", root.getAbsolutePath(), canWrite());
465 }
466
467 public File getRoot() {
468 return root;
469 }
470
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000471 public boolean refresh() throws Exception {
472 init();
473 exec(refresh, root);
Stuart McCullochf3173222012-06-07 21:57:32 +0000474 if (dirty) {
475 dirty = false;
476 return true;
Stuart McCulloch669423b2012-06-26 16:34:24 +0000477 }
478 return false;
Stuart McCullochf3173222012-06-07 21:57:32 +0000479 }
480
481 public String getName() {
482 if (name == null) {
483 return toString();
484 }
485 return name;
486 }
487
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000488 /*
489 * (non-Javadoc)
490 * @see aQute.bnd.service.RepositoryPlugin#get(java.lang.String,
491 * aQute.bnd.version.Version, java.util.Map)
492 */
493 public File get(String bsn, Version version, Map<String,String> properties, DownloadListener... listeners)
494 throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000495 init();
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000496 beforeGet(bsn, version);
497 File file = getLocal(bsn, version, properties);
498 if (file.exists()) {
499 for (DownloadListener l : listeners) {
500 try {
501 l.success(file);
502 }
503 catch (Exception e) {
504 reporter.exception(e, "Download listener for %s", file);
505 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000506 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000507 return file;
Stuart McCullochf3173222012-06-07 21:57:32 +0000508 }
509 return null;
510 }
511
512 public void setRegistry(Registry registry) {
513 this.registry = registry;
514 }
515
516 public String getLocation() {
517 return root.toString();
518 }
519
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000520 public Map<String,Runnable> actions(Object... target) throws Exception {
521 if (target == null || target.length == 0)
522 return null; // no default actions
523
524 try {
525 String bsn = (String) target[0];
526 Version version = (Version) target[1];
527
528 final File f = get(bsn, version, null);
529 if (f == null)
530 return null;
531
532 Map<String,Runnable> actions = new HashMap<String,Runnable>();
533 actions.put("Delete " + bsn + "-" + status(bsn, version), new Runnable() {
534 public void run() {
535 IO.delete(f);
536 if (f.getParentFile().list().length == 0)
537 IO.delete(f.getParentFile());
538 afterAction(f, "delete");
539 };
540 });
541 return actions;
542 }
543 catch (Exception e) {
544 return null;
545 }
546 }
547
548 protected void afterAction(File f, String key) {
549 exec(action, root, f, key);
550 }
551
552 /*
553 * (non-Javadoc)
554 * @see aQute.bnd.service.Actionable#tooltip(java.lang.Object[])
555 */
556 @SuppressWarnings("unchecked")
557 public String tooltip(Object... target) throws Exception {
558 if (target == null || target.length == 0)
559 return String.format("%s\n%s", getName(), root);
560
561 try {
562 String bsn = (String) target[0];
563 Version version = (Version) target[1];
564 Map<String,String> map = null;
565 if (target.length > 2)
566 map = (Map<String,String>) target[2];
567
568 File f = getLocal(bsn, version, map);
569 String s = String.format("Path: %s\nSize: %s\nSHA1: %s", f.getAbsolutePath(), readable(f.length(), 0), SHA1
570 .digest(f).asHex());
571 if (f.getName().endsWith(".lib") && f.isFile()) {
572 s += "\n" + IO.collect(f);
573 }
574 return s;
575
576 }
577 catch (Exception e) {
578 return null;
579 }
580 }
581
582 /*
583 * (non-Javadoc)
584 * @see aQute.bnd.service.Actionable#title(java.lang.Object[])
585 */
586 public String title(Object... target) throws Exception {
587 if (target == null || target.length == 0)
588 return getName();
589
590 if (target.length == 1 && target[0] instanceof String)
591 return (String) target[0];
592
593 if (target.length == 2 && target[0] instanceof String && target[1] instanceof Version) {
594 return status((String) target[0], (Version) target[1]);
595 }
596
597 return null;
598 }
599
600 protected File getLocal(String bsn, Version version, Map<String,String> properties) {
601 File dir = new File(root, bsn);
602
603 File fjar = new File(dir, bsn + "-" + version.getWithoutQualifier() + ".jar");
604 if (fjar.isFile())
605 return fjar.getAbsoluteFile();
606
607 File flib = new File(dir, bsn + "-" + version.getWithoutQualifier() + ".lib");
608 if (flib.isFile())
609 return flib.getAbsoluteFile();
610
611 return fjar.getAbsoluteFile();
612 }
613
614 protected String status(String bsn, Version version) {
615 File file = getLocal(bsn, version, null);
616 StringBuilder sb = new StringBuilder(version.toString());
617 String del = " [";
618
619 if (file.getName().endsWith(".lib")) {
620 sb.append(del).append("L");
621 del = "";
622 }
623 if (!file.getName().endsWith(".jar")) {
624 sb.append(del).append("?");
625 del = "";
626 }
627 if (!file.isFile()) {
628 sb.append(del).append("X");
629 del = "";
630 }
631 if (file.length() == 0) {
632 sb.append(del).append("0");
633 del = "";
634 }
635 if (del.equals(""))
636 sb.append("]");
637 return sb.toString();
638 }
639
640 private static String[] names = {
641 "bytes", "Kb", "Mb", "Gb"
642 };
643
644 private Object readable(long length, int n) {
645 if (length < 0)
646 return "<invalid>";
647
648 if (length < 1024 || n >= names.length)
649 return length + names[n];
650
651 return readable(length / 1024, n + 1);
652 }
653
654 public void close() throws IOException {
655 if (inited)
656 exec(close, root.getAbsolutePath());
657 }
658
659 protected void open() {
660 exec(open, root.getAbsolutePath());
661 }
662
663 protected void beforePut(File tmp) {
664 exec(beforePut, root.getAbsolutePath(), tmp.getAbsolutePath());
665 }
666
667 protected void afterPut(File file, String bsn, Version version, String sha) {
668 exec(afterPut, root.getAbsolutePath(), file.getAbsolutePath(), sha);
669 }
670
671 protected void abortPut(File tmpFile) {
672 exec(abortPut, root.getAbsolutePath(), tmpFile.getAbsolutePath());
673 }
674
675 protected void beforeGet(String bsn, Version version) {
676 exec(beforeGet, root.getAbsolutePath(), bsn, version);
677 }
678
679 protected void fireBundleAdded(Jar jar, File file) {
680 if (registry == null)
681 return;
682 List<RepositoryListenerPlugin> listeners = registry.getPlugins(RepositoryListenerPlugin.class);
683 for (RepositoryListenerPlugin listener : listeners) {
684 try {
685 listener.bundleAdded(this, jar, file);
686 }
687 catch (Exception e) {
688 if (reporter != null)
689 reporter.warning("Repository listener threw an unexpected exception: %s", e);
690 }
691 }
692 }
693
694 /**
695 * Execute a command. Used in different stages so that the repository can be
696 * synced with external tools.
697 *
698 * @param line
699 * @param target
700 */
701 void exec(String line, Object... args) {
702 if (line == null)
703 return;
704
705 try {
706 if (args != null)
707 for (int i = 0; i < args.length; i++) {
708 if (i == 0)
709 line = line.replaceAll("\\$\\{@\\}", args[0].toString());
710 line = line.replaceAll("\\$" + i, args[i].toString());
711 }
712
713 if (shell == null) {
714 shell = System.getProperty("os.name").toLowerCase().indexOf("win") > 0 ? "cmd.exe" : "sh";
715 }
716 Command cmd = new Command(shell);
717
718 if (path != null) {
719 cmd.inherit();
720 String oldpath = cmd.var("PATH");
721 path = path.replaceAll("\\s*,\\s*", File.pathSeparator);
722 path = path.replaceAll("\\$\\{@\\}", oldpath);
723 cmd.var("PATH", path);
724 }
725
726 cmd.setCwd(getRoot());
727 StringBuilder stdout = new StringBuilder();
728 StringBuilder stderr = new StringBuilder();
729 int result = cmd.execute(line, stdout, stderr);
730 if (result != 0) {
731 reporter.error("Command %s failed with %s %s %s", line, result, stdout, stderr);
732 }
733 }
734 catch (Exception e) {
735 e.printStackTrace();
736 reporter.exception(e, e.getMessage());
737 }
738 }
739
740 /*
741 * 8 Set the root directory directly
742 */
743 public void setDir(File repoDir) {
744 this.root = repoDir;
745 }
746
747 /**
748 * Delete an entry from the repository and cleanup the directory
749 *
750 * @param bsn
751 * @param version
752 * @throws Exception
753 */
754 public void delete(String bsn, Version version) throws Exception {
755 assert bsn != null;
756
757 SortedSet<Version> versions;
758 if (version == null)
759 versions = versions(bsn);
760 else
761 versions = new SortedList<Version>(version);
762
763 for (Version v : versions) {
764 File f = getLocal(bsn, version, null);
765 if (!f.isFile())
766 reporter.error("No artifact found for %s:%s", bsn, version);
767 else
768 IO.delete(f);
769 }
770 if ( versions(bsn).isEmpty())
771 IO.delete( new File(root,bsn));
772 }
773
Stuart McCullochf3173222012-06-07 21:57:32 +0000774}