Changeset 3588
- Timestamp:
- Jul 23, 2007, 11:28:10 AM (16 years ago)
- Location:
- trunk
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/doc/src/docbook/userdoc/annotations.xml
r3487 r3588 692 692 </sect2> 693 693 694 <sect2 id="annotations.massimport"> 695 <title>Mass annotation import plug-in</title> 696 697 <para> 698 BASE includes a plug-in for importing annotations to multiple items 699 in one go. The plug-in read annotation values from a simple column-based 700 text file. Ususally, a tab is used as the delimiter between columns. 701 The first row should contain the column headers. One column should contain 702 the name or the external ID of the item. The rest of the columns can each be 703 mapped to an annotation type and contains the annotation values. If a column 704 header exactly match the name of an annotation type, the plug-in will automatically 705 create the mapping, otherwise you must do it manually. You don't have to map 706 all columns if you don't want to. 707 </para> 708 709 <para> 710 Each column can only contain a single annotation value for each row. 711 If you have annotation types that accept multiple values you can map 712 two or more columns to the same annotation type, or you can add an 713 extra row only giving the name and the extra annotation value. 714 Here is a simple example of a valid file with comma as column separator: 715 </para> 716 717 <programlisting> 718 # 'Time' and 'Age' are integer types 719 # 'Subtype' is a string enumeration 720 # 'Comment' is a text type that accept multiple values 721 Name,Time (hours),Age (years),Subtype,Comment 722 Sample #1,0,0,alfa,Very good 723 Sample #2,24,0,beta,Not so bad 724 Sample #2,,,,Yet another comment 725 </programlisting> 726 727 <para> 728 The plug-in can be used with or without a configuration. The configuration 729 keeps the regular expressions and other settings used to parse the file. If 730 you often import annotations from the same file format, we recommend that 731 you use a configuration. The mapping from file columns to annotation types 732 is not part of the configuration, it must be done each time the plug-in is used. 733 </para> 734 735 <para> 736 The plug-in can be used from the list view of all annotatable items. 737 Using the plug-in is a three-step wizard: 738 </para> 739 740 <orderedlist> 741 <listitem> 742 <para> 743 Select a file to import from and the regular expressions and other 744 settings used to parse the file. In this step you also select the column 745 that contains the name or external ID the items. If a configuration is used 746 all settings on this page, except the file to import from, already has values. 747 </para> 748 </listitem> 749 750 <listitem> 751 <para> 752 The plug-in will start parsing the file until it finds the column headers. 753 You are asked to select an annotation type for each column. 754 </para> 755 </listitem> 756 757 <listitem> 758 <para> 759 Set error handling options and some other import options. 760 </para> 761 </listitem> 762 763 </orderedlist> 764 765 </sect2> 766 694 767 </sect1> 695 768 -
trunk/src/plugins/core/net/sf/basedb/plugins/AbstractFlatFileImporter.java
r3586 r3588 534 534 try 535 535 { 536 errorHandler.handleError(t); 536 if (errorHandler == null) 537 { 538 throw t; 539 } 540 else 541 { 542 errorHandler.handleError(t); 543 } 537 544 skippedLines++; 538 545 } … … 565 572 catch (Throwable t) 566 573 { 567 errorHandler.handleError(t); 574 if (errorHandler == null) 575 { 576 throw t; 577 } 578 else 579 { 580 errorHandler.handleError(t); 581 } 568 582 skippedLines++; 569 583 } … … 593 607 catch (Throwable t) 594 608 { 595 errorHandler.handleError(t); 609 if (errorHandler == null) 610 { 611 throw t; 612 } 613 else 614 { 615 errorHandler.handleError(t); 616 } 596 617 skippedLines++; 597 618 } … … 987 1008 Initialise the error handling system. This method is called just before the 988 1009 import is starting. A subclass may override this method to add specific 989 error handlers, but it is important to call <code>super.setUpErrorHandling()</code> 990 or the error handling will not work. The subclass may also add error handlers 991 in the {@link #begin(FlatFileParser)} method. 1010 error handlers. If <code>super.setUpErrorHandling()</code> isn't called 1011 error handling in AbstractFlatFileImporter is disabled and the subclass 1012 must do all it's error handling in it's own code. The subclass may also add 1013 error handlers in the {@link #begin(FlatFileParser)} method. 992 1014 */ 993 1015 protected void setUpErrorHandling() -
trunk/src/plugins/core/net/sf/basedb/plugins/AnnotationFlatFileImporter.java
r3586 r3588 29 29 import java.util.ArrayList; 30 30 import java.util.Arrays; 31 import java.util.Collection; 31 32 import java.util.EnumSet; 32 33 import java.util.HashMap; … … 60 61 import net.sf.basedb.core.ParameterType; 61 62 import net.sf.basedb.core.Permission; 63 import net.sf.basedb.core.PermissionDeniedException; 62 64 import net.sf.basedb.core.PluginParameter; 63 65 import net.sf.basedb.core.RequestInformation; … … 71 73 import net.sf.basedb.core.plugin.ParameterValues; 72 74 import net.sf.basedb.core.plugin.ParameterValuesWrapper; 75 import net.sf.basedb.core.plugin.Permissions; 73 76 import net.sf.basedb.core.plugin.Request; 74 77 import net.sf.basedb.core.plugin.Response; … … 88 91 89 92 /** 93 Plug-in for importing annotations from simple text files. The plug-in supports 94 all files that can be parsed with the {@link FlatFileParser} class. This plug-in 95 works without a configuration but can use a configuration to store regular 96 expressions and other settings for the flat file parser. In both cases, 97 the job configuration is a three-step process: 98 99 <ol> 100 <li>Setup regular expressions and other options for the flat file parser. If a 101 configuration is used all values should already be filled in. In this step 102 a file to import from must also be selected. 103 <li>Map file columns to annotation types. Annotation types that support multiple 104 values may be mapped to more than one column. 105 <li>Setup error handling options and other settings for the plug-in (for example 106 if existing annotations should be replaced or not). 107 </ol> 90 108 91 109 @author nicklas … … 112 130 113 131 private static Set<GuiContext> guiContexts; 114 132 private static final Set<Permissions> permissions = new HashSet<Permissions>(); 133 134 // second step in the wizard 115 135 private static final String CONFIGURE_MAPPING = "configure_mapping"; 136 // third step in the wizard 116 137 private static final String CONFIGURE_IMPORT = "configure_import"; 117 138 … … 147 168 148 169 /** 149 Section definition for grouping all mappings of columns to annotation types 170 Section definition for grouping all mappings of columns to annotation types. 171 Parameters in this section will be generated on the fly based on the 172 column headers from the file to import from. 150 173 */ 151 174 private static final PluginParameter<String> annotationTypeSection = new PluginParameter<String>( … … 199 222 "skip = Skip the current annotation and continue\n"+ 200 223 "fail = Stop with an error message (default)", 201 new StringParameterType(255, " fail", true, 1, 0, 0,224 new StringParameterType(255, "skip", true, 1, 0, 0, 202 225 Arrays.asList( new String[] { "skip", "fail"} )) 203 226 ); … … 214 237 215 238 private static final PluginParameter<String> multipleItemsFoundErrorParameter = new PluginParameter<String>( 216 217 218 219 220 221 222 223 239 "multipleItemsFoundError", 240 "Multiple items found", 241 "What to do if the plug-in finds more than one item with the same name or external ID.\n\n"+ 242 "all = Annotate all items\n"+ 243 "fail = Stop with an error message", 244 new StringParameterType(255, "all", false, 1, 0, 0, 245 Arrays.asList( new String[] { "all", "fail"} )) 246 ); 224 247 225 248 … … 232 255 "skip = Skip the current annotation and continue\n"+ 233 256 "fail = Stop with an error message", 234 new StringParameterType(255, null, false, 1, 0, 0,257 new StringParameterType(255, "crop", false, 1, 0, 0, 235 258 Arrays.asList( new String[] { "crop", "skip", "fail"} )) 236 259 ); … … 304 327 return true; 305 328 } 329 /** 330 Request read access to File:s, read access to annotation types 331 and write access to all annotatable items. 332 */ 333 public Collection<Permissions> getPermissions() 334 { 335 if (permissions.size() == 0) 336 { 337 permissions.add(new Permissions(Item.FILE, null, EnumSet.of(Permission.READ))); 338 permissions.add(new Permissions(Item.ANNOTATIONTYPE, null, EnumSet.of(Permission.READ))); 339 340 for (GuiContext ctx : getGuiContexts()) 341 { 342 permissions.add(new Permissions(ctx.getItem(), null, EnumSet.of(Permission.WRITE))); 343 } 344 } 345 return permissions; 346 } 306 347 // ------------------------------------------- 307 348 … … 310 351 ------------------------------------------- 311 352 */ 353 /** 354 This plug-in works in list context of all {@link Annotatable} items, 355 except bioassay sets, bioassays and wells because they are not standalone 356 items. We use {@link Metadata#getAnnotatableItems()} to create the contexts. 357 */ 312 358 public Set<GuiContext> getGuiContexts() 313 359 { … … 319 365 for (Item item : Metadata.getAnnotatableItems()) 320 366 { 321 guiContexts.add(new GuiContext(item, GuiContext.Type.LIST)); 367 if (item.getDefinedPermissions() != null) 368 { 369 guiContexts.add(new GuiContext(item, GuiContext.Type.LIST)); 370 } 322 371 } 323 372 } … … 327 376 public String isInContext(GuiContext context, Object item) 328 377 { 378 if (!sc.hasPermission(Permission.WRITE, context.getItem())) 379 { 380 throw new PermissionDeniedException(Permission.WRITE, context.getItem().toString()); 381 } 329 382 return null; 330 383 } … … 522 575 private boolean failIfTooManyValues; 523 576 577 // Messaging 578 private int numItems; 579 private int numItemNotFound; 580 private int numAnnotations; 581 private int numReplaced; 582 private int numError; 583 524 584 @Override 585 /** 586 The flat file parser is initialised with settings from either the job or configuration. 587 */ 525 588 protected FlatFileParser getInitializedFlatFileParser() 526 589 throws BaseException … … 530 593 } 531 594 595 /** 596 Don't use AbstractFlatFileImporter to handle errors. 597 */ 598 @Override 599 protected void setUpErrorHandling() 600 {} 601 602 /** 603 Setup error handling and pre-load some of the configuration options. 604 */ 532 605 @Override 533 606 protected void begin(FlatFileParser ffp) … … 558 631 } 559 632 633 /** 634 Setup column mapping. Creates DbControl and query to find items. 635 */ 560 636 @Override 561 637 protected void beginData() … … 597 673 } 598 674 675 /** 676 Read annotations from a single data line. Errors are handled internally. 677 Errors thrown from this method should be reported back to client (ie. error 678 handling in AbstractFlatFileParser) must be disabled. This method will 679 load items and annotation values and put them in an internal cache. No 680 items are annotated until the entire file has been parsed. 681 */ 599 682 @Override 600 683 protected void handleData(Data data) … … 602 685 { 603 686 604 // Find the item(s) to annotate 687 // Find the item(s) to annotate; mapper gives us name OR external ID 605 688 String name = itemMapper.getValue(data); 689 // 1. check the cache 606 690 Set<NewAnnotations> items = itemCache.get(name); 607 691 if (items == null) 608 692 { 609 693 items = new HashSet<NewAnnotations>(); 694 // The 'name' parameter can also query against the external ID 610 695 itemQuery.setParameter("name", name, Type.STRING); 611 696 List<?> result = itemQuery.list(dc); 612 if (result.isEmpty() && !ignoreNotFoundItems) 613 { 614 throw new ItemNotFoundException(itemType + 615 "[" + (searchExternalId ? "externalId" : "name") + "=" + name + "]"); 697 if (result.isEmpty()) 698 { 699 // No item with specified name/external ID was found 700 if (!ignoreNotFoundItems) 701 { 702 throw new ItemNotFoundException(itemType + 703 "[" + (searchExternalId ? "externalId" : "name") + "=" + name + "]"); 704 } 705 else 706 { 707 numItemNotFound++; 708 } 616 709 } 617 710 if (result.size() > 1 && failIfMultipleFoundItems) … … 622 715 for (Object item : result) 623 716 { 717 // Add each item that was found to the cache 624 718 items.add(new NewAnnotations((Annotatable)item)); 625 719 } … … 629 723 if (!items.isEmpty()) 630 724 { 631 // Load annotation values 725 // Load annotation values from column mappers 632 726 for (Map.Entry<Mapper, AnnotationType> entry : mappers.entrySet()) 633 727 { … … 641 735 if (at.isEnumeration() && at.getValueType() == Type.STRING) 642 736 { 737 // Case and leading/trailing whitespace are ignore for enumerated string annotations 643 738 String enumValue = at.findValue(sValue, true, true); 644 739 if (enumValue != null) sValue = enumValue; … … 650 745 for (NewAnnotations item : items) 651 746 { 747 // Add annotation value to the cache 652 748 item.addValue(at, annotationValue); 653 749 } … … 656 752 catch (Throwable t) 657 753 { 754 // In case the annotation value is not valid... 755 numError++; 658 756 try 659 757 { 660 758 errorHandler.handleError(t); 759 // The error should be ignored if we get passed the above line 661 760 } 662 761 catch (Throwable t2) 663 762 { 763 // handleData doesn't allow us to throw any Throwable 664 764 if (t2 instanceof RuntimeException) 665 765 { … … 676 776 } 677 777 778 /** 779 Now it's time to update the items in the cache with the new annotation 780 values. 781 */ 678 782 @Override 679 783 protected void end(boolean success) … … 685 789 for (Set<NewAnnotations> n : itemCache.values()) 686 790 { 791 numItems += n.size(); 687 792 for (NewAnnotations na : n) 688 793 { 689 794 na.setNewAnnotations(addToUnlimited, replaceExisting, failIfTooManyValues); 795 if (na.getNumSet() == 0) numItems--; 796 numAnnotations += na.getNumSet(); 797 numError += na.getNumError(); 798 numReplaced += na.getNumReplaced(); 690 799 } 691 800 } … … 700 809 finally 701 810 { 702 dc.close();811 if (dc != null) dc.close(); 703 812 super.end(success); 704 813 ffp = null; 705 814 mappers.clear(); 706 815 itemCache.clear(); 707 708 } 816 } 817 } 818 @Override 819 protected String getSuccessMessage(int skippedLines) 820 { 821 String msg = numItems + " item(s) annotated with " + numAnnotations + " annotation(s)"; 822 if (numReplaced > 0) msg += " (" + numReplaced + " was replaced)"; 823 if (numItemNotFound > 0) msg += "; " + numItemNotFound + " row(s) skipped because no item was found"; 824 if (numError > 0) msg += "; " + numError + " annotation(s) skipped because of invalid values"; 825 return msg; 709 826 } 710 827 // ------------------------------------------- … … 936 1053 937 1054 /** 938 Check if the current item has an 'externalId' property. 1055 Check if the current item has an 'externalId' property. We use reflection to 1056 look for the 'getExternalId' method in the item's data class. 939 1057 */ 940 1058 private boolean hasExternalId(Item item) … … 951 1069 } 952 1070 1071 /** 1072 Create a query that return items of the specified type. We use reflection 1073 to call the static method 'getQuery' on the item's item class, for 1074 example {@link net.sf.basedb.core.Sample#getQuery()}. We add a restriction 1075 to either the 'name' or the 'externalId' property but the parameter is 1076 always called 'name' to make it easier to implement the code that is 1077 using the query. 1078 1079 @param itemType The type of items to search 1080 @param searchExternalId If we should search by name or external ID 1081 @param includes Include options passed to {@link ItemQuery#include(java.util.Collection)} 1082 @return A query that searches items of the specified type. 1083 */ 953 1084 private ItemQuery<?> createQuery(Item itemType, boolean searchExternalId, Set<Include> includes) 954 1085 { … … 989 1120 } 990 1121 1122 /** 1123 Internal cache for storing annotation values from the file 1124 for a single item. The items are not annotated until the entire 1125 file has been parsed. 1126 */ 991 1127 private static class NewAnnotations 992 1128 { 993 1129 private final Annotatable item; 994 1130 private final Map<AnnotationType, List<Object>> values; 995 1131 private int numAnnotations; 1132 private int numSet; 1133 private int numError; 1134 private int numReplaced; 1135 1136 /** 1137 Create a new cache for the specified item. 1138 */ 996 1139 private NewAnnotations(Annotatable item) 997 1140 { 998 1141 this.item = item; 999 1142 this.values = new HashMap<AnnotationType, List<Object>>(); 1000 } 1001 1143 this.numAnnotations = 0; 1144 } 1145 1146 /** 1147 Add an annotation value. The value should have been checked for 1148 error before calling this method. 1149 */ 1002 1150 private void addValue(AnnotationType at, Object value) 1003 1151 { … … 1009 1157 } 1010 1158 listOfValues.add(value); 1011 } 1012 1159 numAnnotations++; 1160 } 1161 1162 /** 1163 The total numer of annotation values that has been added to this 1164 cache. 1165 */ 1166 private int getNumAnnotations() 1167 { 1168 return numAnnotations; 1169 } 1170 1171 /** 1172 Annotate the item with the annotation values from the file. 1173 @param addToUnlimited TRUE to append to annotation types with multiplicity==0, FALSE 1174 to replace 1175 @param replaceExisting TRUE to replace existing annotation values, FALSE to skip 1176 @param failIfTooManyValues FALSE to skip annotations with more values than 1177 the multiplicity settings allows, TRUE to throw an exception 1178 */ 1013 1179 private void setNewAnnotations(boolean addToUnlimited, boolean replaceExisting, boolean failIfTooManyValues) 1014 1180 { 1015 1181 if (values.size() == 0) return; 1182 int numAnnotations = 0; 1016 1183 AnnotationSet as = item.getAnnotationSet(); 1017 1184 for (Map.Entry<AnnotationType, List<Object>> entry : values.entrySet()) … … 1019 1186 AnnotationType at = entry.getKey(); 1020 1187 List<Object> newValues = entry.getValue(); 1188 int size = newValues.size(); 1021 1189 int multiplicity = at.getMultiplicity(); 1022 1190 boolean hasAnnotation = as.hasAnnotation(at); … … 1028 1196 { 1029 1197 Annotation a = as.getAnnotation(at); 1030 newValues.addAll(0, a.getValues()); 1031 a.setValues(newValues); 1198 if (merge) newValues.addAll(0, a.getValues()); 1199 a.setValues(newValues); 1200 numSet += size; 1201 if (hasAnnotation && !merge) numReplaced += size; 1032 1202 } 1033 } 1034 } 1035 } 1036 } 1037 1038 1203 else 1204 { 1205 numError += size; 1206 } 1207 } 1208 } 1209 } 1210 1211 /** 1212 Number of annotations that was set (=added or replaced) by the call to the 1213 {@link #setNewAnnotations(boolean, boolean, boolean)} method. 1214 */ 1215 private int getNumSet() 1216 { 1217 return numSet; 1218 } 1219 1220 /** 1221 Number of annotations that was replaced by the call to the 1222 {@link #setNewAnnotations(boolean, boolean, boolean)} method. 1223 */ 1224 private int getNumReplaced() 1225 { 1226 return numReplaced; 1227 } 1228 1229 /** 1230 Number of annotations that was couldn't be set by the call to the 1231 {@link #setNewAnnotations(boolean, boolean, boolean)} method because of an 1232 error (too many values). 1233 */ 1234 private int getNumError() 1235 { 1236 return numError; 1237 } 1238 1239 } 1039 1240 } -
trunk/www/admin/plugindefinitions/edit_plugin.jsp
r3501 r3588 85 85 Set<Permission> defined, Set<Permission> always, Set<Permission> maybe) 86 86 { 87 if (defined .contains(p))87 if (defined != null && defined.contains(p)) 88 88 { 89 89 if (always != null && always.contains(p)) … … 688 688 { 689 689 Item ppType = pp.getItemType(); 690 Set<Permission> always = Permission.expand(pp.getAlwaysGranted()); 691 Set<Permission> maybe = Permission.expand(pp.getMaybeGranted()); 692 %> 693 frm['<%=ppType.name()%>_granted'].value = <%=always == null ? 0 : PermissionUtil.getPermissionCode(always)%>; 694 frm['<%=ppType.name()%>_denied'].value = <%=maybe == null ? 255 : 255 & ~PermissionUtil.getPermissionCode(maybe)%>; 695 <% 690 if (ppType.getDefinedPermissions() != null) 691 { 692 Set<Permission> always = Permission.expand(pp.getAlwaysGranted()); 693 Set<Permission> maybe = Permission.expand(pp.getMaybeGranted()); 694 %> 695 frm['<%=ppType.name()%>_granted'].value = <%=always == null ? 0 : PermissionUtil.getPermissionCode(always)%>; 696 frm['<%=ppType.name()%>_denied'].value = <%=maybe == null ? 255 : 255 & ~PermissionUtil.getPermissionCode(maybe)%>; 697 <% 698 } 696 699 } 697 700 %> … … 770 773 { 771 774 %> 772 773 775 <div class="error"><%=warning%></div> 774 776 <% … … 827 829 <td> 828 830 <table> 829 <tr ><td>831 <tr valign="top"><td> 830 832 <b>Always grant</b><br> 831 833 <input type="checkbox" name="grant_create" onClick="permissionOnClick(this)">Create<br> … … 861 863 Set<Permission> maybe = Permission.expand(pp.getMaybeGranted()); 862 864 Set<Permission> defined = ppType.getDefinedPermissions(); 863 StringBuilder sb = new StringBuilder(); 864 sb.append("["); 865 appendPermissionLetter(sb, Permission.CREATE, "C", defined, always, maybe); 866 appendPermissionLetter(sb, Permission.READ, "R", defined, always, maybe); 867 appendPermissionLetter(sb, Permission.USE, "U", defined, always, maybe); 868 appendPermissionLetter(sb, Permission.WRITE, "W", defined, always, maybe); 869 appendPermissionLetter(sb, Permission.DELETE, "D", defined, always, maybe); 870 appendPermissionLetter(sb, Permission.SET_OWNER, "O", defined, always, maybe); 871 appendPermissionLetter(sb, Permission.SET_PERMISSION, "P", defined, always, maybe); 872 sb.append("]"); 873 %> 874 <tr> 875 <td><%=ppType%></td> 876 <td><%=sb.toString()%></td> 877 </tr> 878 <% 865 if (defined != null) 866 { 867 StringBuilder sb = new StringBuilder(); 868 sb.append("["); 869 appendPermissionLetter(sb, Permission.CREATE, "C", defined, always, maybe); 870 appendPermissionLetter(sb, Permission.READ, "R", defined, always, maybe); 871 appendPermissionLetter(sb, Permission.USE, "U", defined, always, maybe); 872 appendPermissionLetter(sb, Permission.WRITE, "W", defined, always, maybe); 873 appendPermissionLetter(sb, Permission.DELETE, "D", defined, always, maybe); 874 appendPermissionLetter(sb, Permission.SET_OWNER, "O", defined, always, maybe); 875 appendPermissionLetter(sb, Permission.SET_PERMISSION, "P", defined, always, maybe); 876 sb.append("]"); 877 %> 878 <tr> 879 <td><%=ppType%></td> 880 <td><%=sb.toString()%></td> 881 </tr> 882 <% 883 } 879 884 } 880 885 %>
Note: See TracChangeset
for help on using the changeset viewer.