blob: 08e5d0213fabd8088533913f431c2500726bf3fa [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 McCullochb7f3d7b2012-09-19 12:56:05 +0000297 Jar tmpJar = 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 McCullochb7f3d7b2012-09-19 12:56:05 +0000301 String bsn = tmpJar.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 McCullochb7f3d7b2012-09-19 12:56:05 +0000305 String versionString = tmpJar.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 McCullochb7f3d7b2012-09-19 12:56:05 +0000325 // An open jar on file will fail rename on windows
326 tmpJar.close();
327
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000328 IO.rename(tmpFile, file);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000329
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000330 fireBundleAdded(file);
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000331 afterPut(file, bsn, version, Hex.toHexString(digest));
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000332
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000333 // TODO like to beforeGet rid of the latest option. This is only
334 // used to have a constant name for the outside users (like ant)
335 // we should be able to handle this differently?
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000336 File latest = new File(dir, bsn + "-latest.jar");
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000337 IO.copy(file, latest);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000338
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000339 reporter.trace("updated %s", file.getAbsolutePath());
340
341 return file;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000342 }
343 finally {
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000344 tmpJar.close();
Stuart McCullochf3173222012-06-07 21:57:32 +0000345 }
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000346 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000347
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000348 /*
349 * (non-Javadoc)
350 * @see aQute.bnd.service.RepositoryPlugin#put(java.io.InputStream,
351 * aQute.bnd.service.RepositoryPlugin.PutOptions)
352 */
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000353 public PutResult put(InputStream stream, PutOptions options) throws Exception {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000354 /* determine if the put is allowed */
355 if (!canWrite) {
356 throw new IOException("Repository is read-only");
357 }
358
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000359 assert stream != null;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000360
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000361 if (options == null)
362 options = DEFAULTOPTIONS;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000363
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000364 init();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000365
366 /*
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000367 * copy the artifact from the (new/digest) stream into a temporary file
368 * in the root directory of the repository
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000369 */
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000370 File tmpFile = IO.createTempFile(root, "put", ".jar");
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000371 try {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000372 DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("SHA-1"));
373 try {
374 IO.copy(dis, tmpFile);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000375
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000376 byte[] digest = dis.getMessageDigest().digest();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000377
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000378 if (options.digest != null && !Arrays.equals(digest, options.digest))
379 throw new IOException("Retrieved artifact digest doesn't match specified digest");
380
381 /*
382 * put the artifact into the repository (from the temporary
383 * file)
384 */
385 beforePut(tmpFile);
386 File file = putArtifact(tmpFile, digest);
387 file.setReadOnly();
388
389 PutResult result = new PutResult();
390 result.digest = digest;
391 result.artifact = file.toURI();
392
393 return result;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000394 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000395 finally {
396 dis.close();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000397 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000398 }
399 catch (Exception e) {
400 abortPut(tmpFile);
401 throw e;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000402 }
403 finally {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000404 IO.delete(tmpFile);
Stuart McCullochf3173222012-06-07 21:57:32 +0000405 }
406 }
407
408 public void setLocation(String string) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000409 root = IO.getFile(string);
Stuart McCullochf3173222012-06-07 21:57:32 +0000410 }
411
412 public void setReporter(Reporter reporter) {
413 this.reporter = reporter;
414 }
415
416 public List<String> list(String regex) throws Exception {
417 init();
418 Instruction pattern = null;
419 if (regex != null)
420 pattern = new Instruction(regex);
421
422 List<String> result = new ArrayList<String>();
423 if (root == null) {
424 if (reporter != null)
425 reporter.error("FileRepo root directory is not set.");
426 } else {
427 File[] list = root.listFiles();
428 if (list != null) {
429 for (File f : list) {
430 if (!f.isDirectory())
431 continue; // ignore non-directories
432 String fileName = f.getName();
433 if (fileName.charAt(0) == '.')
434 continue; // ignore hidden files
435 if (pattern == null || pattern.matches(fileName))
436 result.add(fileName);
437 }
438 } else if (reporter != null)
439 reporter.error("FileRepo root directory (%s) does not exist", root);
440 }
441
442 return result;
443 }
444
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000445 public SortedSet<Version> versions(String bsn) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000446 init();
447 File dir = new File(root, bsn);
448 if (dir.isDirectory()) {
449 String versions[] = dir.list();
450 List<Version> list = new ArrayList<Version>();
451 for (String v : versions) {
452 Matcher m = REPO_FILE.matcher(v);
453 if (m.matches()) {
454 String version = m.group(2);
455 if (version.equals("latest"))
456 version = Integer.MAX_VALUE + "";
457 list.add(new Version(version));
458 }
459 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000460 return new SortedList<Version>(list);
Stuart McCullochf3173222012-06-07 21:57:32 +0000461 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000462 return SortedList.empty();
Stuart McCullochf3173222012-06-07 21:57:32 +0000463 }
464
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000465 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000466 public String toString() {
467 return String.format("%-40s r/w=%s", root.getAbsolutePath(), canWrite());
468 }
469
470 public File getRoot() {
471 return root;
472 }
473
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000474 public boolean refresh() throws Exception {
475 init();
476 exec(refresh, root);
Stuart McCullochf3173222012-06-07 21:57:32 +0000477 if (dirty) {
478 dirty = false;
479 return true;
Stuart McCulloch669423b2012-06-26 16:34:24 +0000480 }
481 return false;
Stuart McCullochf3173222012-06-07 21:57:32 +0000482 }
483
484 public String getName() {
485 if (name == null) {
486 return toString();
487 }
488 return name;
489 }
490
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000491 /*
492 * (non-Javadoc)
493 * @see aQute.bnd.service.RepositoryPlugin#get(java.lang.String,
494 * aQute.bnd.version.Version, java.util.Map)
495 */
496 public File get(String bsn, Version version, Map<String,String> properties, DownloadListener... listeners)
497 throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000498 init();
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000499 beforeGet(bsn, version);
500 File file = getLocal(bsn, version, properties);
501 if (file.exists()) {
502 for (DownloadListener l : listeners) {
503 try {
504 l.success(file);
505 }
506 catch (Exception e) {
507 reporter.exception(e, "Download listener for %s", file);
508 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000509 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000510 return file;
Stuart McCullochf3173222012-06-07 21:57:32 +0000511 }
512 return null;
513 }
514
515 public void setRegistry(Registry registry) {
516 this.registry = registry;
517 }
518
519 public String getLocation() {
520 return root.toString();
521 }
522
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000523 public Map<String,Runnable> actions(Object... target) throws Exception {
524 if (target == null || target.length == 0)
525 return null; // no default actions
526
527 try {
528 String bsn = (String) target[0];
529 Version version = (Version) target[1];
530
531 final File f = get(bsn, version, null);
532 if (f == null)
533 return null;
534
535 Map<String,Runnable> actions = new HashMap<String,Runnable>();
536 actions.put("Delete " + bsn + "-" + status(bsn, version), new Runnable() {
537 public void run() {
538 IO.delete(f);
539 if (f.getParentFile().list().length == 0)
540 IO.delete(f.getParentFile());
541 afterAction(f, "delete");
542 };
543 });
544 return actions;
545 }
546 catch (Exception e) {
547 return null;
548 }
549 }
550
551 protected void afterAction(File f, String key) {
552 exec(action, root, f, key);
553 }
554
555 /*
556 * (non-Javadoc)
557 * @see aQute.bnd.service.Actionable#tooltip(java.lang.Object[])
558 */
559 @SuppressWarnings("unchecked")
560 public String tooltip(Object... target) throws Exception {
561 if (target == null || target.length == 0)
562 return String.format("%s\n%s", getName(), root);
563
564 try {
565 String bsn = (String) target[0];
566 Version version = (Version) target[1];
567 Map<String,String> map = null;
568 if (target.length > 2)
569 map = (Map<String,String>) target[2];
570
571 File f = getLocal(bsn, version, map);
572 String s = String.format("Path: %s\nSize: %s\nSHA1: %s", f.getAbsolutePath(), readable(f.length(), 0), SHA1
573 .digest(f).asHex());
574 if (f.getName().endsWith(".lib") && f.isFile()) {
575 s += "\n" + IO.collect(f);
576 }
577 return s;
578
579 }
580 catch (Exception e) {
581 return null;
582 }
583 }
584
585 /*
586 * (non-Javadoc)
587 * @see aQute.bnd.service.Actionable#title(java.lang.Object[])
588 */
589 public String title(Object... target) throws Exception {
590 if (target == null || target.length == 0)
591 return getName();
592
593 if (target.length == 1 && target[0] instanceof String)
594 return (String) target[0];
595
596 if (target.length == 2 && target[0] instanceof String && target[1] instanceof Version) {
597 return status((String) target[0], (Version) target[1]);
598 }
599
600 return null;
601 }
602
603 protected File getLocal(String bsn, Version version, Map<String,String> properties) {
604 File dir = new File(root, bsn);
605
606 File fjar = new File(dir, bsn + "-" + version.getWithoutQualifier() + ".jar");
607 if (fjar.isFile())
608 return fjar.getAbsoluteFile();
609
610 File flib = new File(dir, bsn + "-" + version.getWithoutQualifier() + ".lib");
611 if (flib.isFile())
612 return flib.getAbsoluteFile();
613
614 return fjar.getAbsoluteFile();
615 }
616
617 protected String status(String bsn, Version version) {
618 File file = getLocal(bsn, version, null);
619 StringBuilder sb = new StringBuilder(version.toString());
620 String del = " [";
621
622 if (file.getName().endsWith(".lib")) {
623 sb.append(del).append("L");
624 del = "";
625 }
626 if (!file.getName().endsWith(".jar")) {
627 sb.append(del).append("?");
628 del = "";
629 }
630 if (!file.isFile()) {
631 sb.append(del).append("X");
632 del = "";
633 }
634 if (file.length() == 0) {
635 sb.append(del).append("0");
636 del = "";
637 }
638 if (del.equals(""))
639 sb.append("]");
640 return sb.toString();
641 }
642
643 private static String[] names = {
644 "bytes", "Kb", "Mb", "Gb"
645 };
646
647 private Object readable(long length, int n) {
648 if (length < 0)
649 return "<invalid>";
650
651 if (length < 1024 || n >= names.length)
652 return length + names[n];
653
654 return readable(length / 1024, n + 1);
655 }
656
657 public void close() throws IOException {
658 if (inited)
659 exec(close, root.getAbsolutePath());
660 }
661
662 protected void open() {
663 exec(open, root.getAbsolutePath());
664 }
665
666 protected void beforePut(File tmp) {
667 exec(beforePut, root.getAbsolutePath(), tmp.getAbsolutePath());
668 }
669
670 protected void afterPut(File file, String bsn, Version version, String sha) {
671 exec(afterPut, root.getAbsolutePath(), file.getAbsolutePath(), sha);
672 }
673
674 protected void abortPut(File tmpFile) {
675 exec(abortPut, root.getAbsolutePath(), tmpFile.getAbsolutePath());
676 }
677
678 protected void beforeGet(String bsn, Version version) {
679 exec(beforeGet, root.getAbsolutePath(), bsn, version);
680 }
681
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000682 protected void fireBundleAdded(File file) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000683 if (registry == null)
684 return;
685 List<RepositoryListenerPlugin> listeners = registry.getPlugins(RepositoryListenerPlugin.class);
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000686 Jar jar = null;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000687 for (RepositoryListenerPlugin listener : listeners) {
688 try {
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000689 if (jar == null)
690 jar = new Jar(file);
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000691 listener.bundleAdded(this, jar, file);
692 }
693 catch (Exception e) {
694 if (reporter != null)
695 reporter.warning("Repository listener threw an unexpected exception: %s", e);
696 }
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000697 finally {
698 if (jar != null)
699 jar.close();
700 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000701 }
702 }
703
704 /**
705 * Execute a command. Used in different stages so that the repository can be
706 * synced with external tools.
707 *
708 * @param line
709 * @param target
710 */
711 void exec(String line, Object... args) {
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000712 if (line == null) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000713 return;
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000714 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000715
716 try {
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000717 if (args != null) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000718 for (int i = 0; i < args.length; i++) {
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000719 if (i == 0) {
720 // replaceAll backslash magic ensures windows paths
721 // remain intact
722 line = line.replaceAll("\\$\\{@\\}", args[0].toString().replaceAll("\\\\", "\\\\\\\\"));
723 }
724 // replaceAll backslash magic ensures windows paths remain
725 // intact
726 line = line.replaceAll("\\$" + i, args[i].toString().replaceAll("\\\\", "\\\\\\\\"));
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000727 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000728 }
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000729 // purge remaining placeholders
730 line = line.replaceAll("\\s*\\$[0-9]\\s*", "");
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000731
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000732 int result = 0;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000733 StringBuilder stdout = new StringBuilder();
734 StringBuilder stderr = new StringBuilder();
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000735 if (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) {
736
737 // FIXME ignoring possible shell setting stdin approach used
738 // below does not work in windows
739 Command cmd = new Command("cmd.exe /C " + line);
740 cmd.setCwd(getRoot());
741 result = cmd.execute(stdout, stderr);
742
743 } else {
744 if (shell == null) {
745 shell = "sh";
746 }
747 Command cmd = new Command(shell);
748 cmd.setCwd(getRoot());
749
750 if (path != null) {
751 cmd.inherit();
752 String oldpath = cmd.var("PATH");
753 path = path.replaceAll("\\s*,\\s*", File.pathSeparator);
754 path = path.replaceAll("\\$\\{@\\}", oldpath);
755 cmd.var("PATH", path);
756 }
757 result = cmd.execute(line, stdout, stderr);
758 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000759 if (result != 0) {
760 reporter.error("Command %s failed with %s %s %s", line, result, stdout, stderr);
761 }
762 }
763 catch (Exception e) {
764 e.printStackTrace();
765 reporter.exception(e, e.getMessage());
766 }
767 }
768
769 /*
770 * 8 Set the root directory directly
771 */
772 public void setDir(File repoDir) {
773 this.root = repoDir;
774 }
775
776 /**
777 * Delete an entry from the repository and cleanup the directory
778 *
779 * @param bsn
780 * @param version
781 * @throws Exception
782 */
783 public void delete(String bsn, Version version) throws Exception {
784 assert bsn != null;
785
786 SortedSet<Version> versions;
787 if (version == null)
788 versions = versions(bsn);
789 else
790 versions = new SortedList<Version>(version);
791
792 for (Version v : versions) {
793 File f = getLocal(bsn, version, null);
794 if (!f.isFile())
795 reporter.error("No artifact found for %s:%s", bsn, version);
796 else
797 IO.delete(f);
798 }
799 if ( versions(bsn).isEmpty())
800 IO.delete( new File(root,bsn));
801 }
802
Stuart McCullochf3173222012-06-07 21:57:32 +0000803}