Changeset 5675
- Timestamp:
- Jun 28, 2011, 3:19:45 PM (12 years ago)
- Location:
- trunk
- Files:
-
- 4 added
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/build.xml
r5635 r5675 1343 1343 <property name="base.version" value="${base.version}${base.versionsuffix}"/> 1344 1344 </ant> 1345 <copy todir="${docbook.html.out}"> 1346 <fileset dir="${docbook.src}" includes="css/*.*" /> 1347 <fileset dir="${docbook.src}" includes="script/*.*" /> 1345 <copy todir="${docbook.html.out}"> 1346 <fileset dir="${docbook.src}" includes="css/*.*" /> 1347 <fileset dir="${docbook.src}" includes="script/*.*" /> 1348 <fileset dir="${docbook.src}" includes="examples/*.*" /> 1348 1349 </copy> 1349 1350 -
trunk/doc/historical/development/coding/item/index.html
r4889 r5675 4 4 5 5 Copyright (C) 2005 Nicklas Nordborg 6 Copyright (C) 2006 Jari H äkkinen, Nicklas Nordborg6 Copyright (C) 2006 Jari H�kkinen, Nicklas Nordborg 7 7 8 8 This file is part of BASE - BioArray Software Environment. … … 28 28 </head> 29 29 30 <body >30 <body class="old"> 31 31 32 32 <div class="navigation"> … … 39 39 40 40 <h1>Coding rules and guidelines for item classes</h1> 41 <div class="warning"> 42 NOTE! This document is outdated and has been replaced with newer 43 documentation. See <a href="../../../../html/developerdoc/core_ref/core_ref.rules.itemclass.html">General coding style guidelines</a> 44 </div> 41 45 42 46 <div class="abstract"> -
trunk/doc/src/docbook/developerdoc/core_ref.xml
r5673 r5675 2336 2336 <sect2 id="core_ref.rules.itemclass"> 2337 2337 <title>Item-class rules</title> 2338 2338 2339 <para> 2339 This documentation is only available in the old format. 2340 See <ulink url="http://base.thep.lu.se/chrome/site/doc/historical/development/coding/item/index.html" 2341 >http://base.thep.lu.se/chrome/site/doc/historical/development/coding/item/index.html</ulink> 2340 This document contains important information about item classes for the BASE developer. 2341 Item classes are classes that handles the business logic for the data classes in 2342 the <code>net.sf.basedb.core.data</code> package. In general there is one item class for 2343 each data class. When extending the database and creating new classes it is important 2344 that it follows the design of the already existing code. 2342 2345 </para> 2346 2347 <sect3 id="core_ref.rules.itemclass.basic"> 2348 <title>Basic class and interface hierarchy</title> 2349 2350 <para> 2351 To simplify the development of items, we have created a set of abstract classes 2352 and interfaces. A real class for an item must inherit from one of those classes 2353 and may implement any of the interfaces if needed. The strucure is similar to the 2354 structure found in the <code>net.sf.basedb.core.data</code> package 2355 (See <xref linkend="api_overview.data_api" />). 2356 </para> 2357 2358 <figure id="core_ref.figures.basic"> 2359 <title>Basic class and interface hierarchy</title> 2360 <screenshot> 2361 <mediaobject> 2362 <imageobject> 2363 <imagedata 2364 align="center" 2365 fileref="figures/uml/corelayer.basic.png" format="PNG" /> 2366 </imageobject> 2367 </mediaobject> 2368 </screenshot> 2369 </figure> 2370 2371 </sect3> 2372 2373 <sect3 id="core_ref.rules.itemclass.permissions"> 2374 <title>Access permissions</title> 2375 <para> 2376 Each item class must be prepared to handle the access permissions for the 2377 logged in user. The base classes will do most of the required work, but 2378 not everything. There are four cases which the item class must be aware of: 2379 </para> 2380 2381 <itemizedlist> 2382 <listitem><para>Initialise permissions in the <methodname>initPermissions()</methodname> method.</para></listitem> 2383 <listitem><para>Check for <emphasis>write</emphasis> permission in settter methods.</para></listitem> 2384 <listitem><para>Check for <emphasis>use</emphasis> permission when creating associations to other items.</para></listitem> 2385 <listitem><para>Make sure the <methodname>getQuery()</methodname> method returns only items with at least read permission.</para></listitem> 2386 </itemizedlist> 2387 2388 <bridgehead id="core_ref.rules.itemclass.initpermissions">Initialise permissions</bridgehead> 2389 2390 <para> 2391 The permissions for an item are initialised by a call to the <methodname>initPermissions()</methodname> 2392 method. This method is called as soon as the item becomes attached to a <classname 2393 docapi="net.sf.basedb.core">DbControl</classname> object, which is responsible for managing items 2394 in the database. The <methodname>initPermissions()</methodname> method shuld be overridden by 2395 subclasses that needs to grant or deny permissions that is not granted or denied by default. 2396 When overriding the <methodname>initPermissions()</methodname> method it is important to: 2397 </para> 2398 2399 <itemizedlist> 2400 <listitem> 2401 <para> 2402 Combine the additional permissions with those that was passed as parameters. 2403 Use the binary OR operator ( | ) with the result from the <code>Permission.grant()</code> 2404 and <code>Permission.deny()</code> methods to do this. 2405 </para> 2406 </listitem> 2407 <listitem> 2408 <para> 2409 Call <code>super.initPermissions()</code>. Otherwise, no permissions will be set all, 2410 resulting in an <classname docapi="net.sf.basedb.core">PermissionDeniedException</classname> almost immediately. 2411 </para> 2412 </listitem> 2413 </itemizedlist> 2414 2415 <para> 2416 Here is an example from the <classname docapi="net.sf.basedb.core">OwnedItem</classname> class. 2417 If the currently logged in user is the same as the owner of the item, <constant>DELETE</constant>, 2418 <constant>SET_OWNER</constant> and <constant>SET_PERMISSION</constant> permissions are granted. 2419 Remember that delete permission also implies <constant>READ</constant>, <constant>USE</constant> 2420 and <constant>WRITE</constant> permissions. 2421 </para> 2422 2423 <programlisting language="java"> 2424 <![CDATA[ 2425 // OwnedItem.java 2426 void initPermissions(int granted, int denied) 2427 { 2428 UserData owner = getData().getOwner(); 2429 // owner may be null for new items 2430 if (owner != null && owner.getId() == getSessionControl().getLoggedInUserId()) 2431 { 2432 granted |= Permission.grant(Permission.DELETE, Permission.SET_OWNER, 2433 Permission.SET_PERMISSION); 2434 } 2435 super.initPermissions(granted, denied); 2436 } 2437 ]]> 2438 </programlisting> 2439 2440 2441 <para> 2442 Here is another example for <classname docapi="net.sf.basedb.core">News</classname> items, 2443 which grants read permission to anyone (even if not logged in) if today is between 2444 the start and end date of the news entry: 2445 </para> 2446 2447 <programlisting language="java"> 2448 <![CDATA[ 2449 // News.java 2450 void initPermissions(int granted, int denied) 2451 throws BaseException 2452 { 2453 long today = new Date().getTime(); 2454 long startDate = getData().getStartDate().getTime(); 2455 long endDate = getData().getEndDate() == null ? 0 : getData().getEndDate().getTime()+24*3600*1000; 2456 if (startDate <= today && (endDate == 0 || today <= endDate)) 2457 { 2458 granted |= Permission.grant(Permission.READ); 2459 } 2460 super.initPermissions(granted, denied); 2461 } 2462 ]]> 2463 </programlisting> 2464 2465 <para> 2466 A third example from the <classname docapi="net.sf.basedb.core">Help</classname> class which 2467 is a child item to <classname docapi="net.sf.basedb.core">Client</classname>. Normally you will 2468 get <constant>READ</constant> permission on all child items if you have <constant>READ</constant> 2469 permission on the parent item, and <constant>CREATE</constant>, <constant>WRITE</constant> and 2470 <constant>DELETE</constant> permissions if you have <constant>WRITE</constant> permission on the parent item. 2471 In this case you don't have to override the <methodname>initPermissions()</methodname> method 2472 if the child class extends the <classname docapi="net.sf.basedb.core">ChildItem</classname> class. 2473 Instead, it should implement the <methodname>getSharedParent()</methodname> 2474 method. The <methodname>ChildItem.initPermissions()</methodname> will take care of checking the 2475 permissions on the parent instead of on the child. Note that this only works if the parent itself 2476 hasn't overridden the <methodname>initPermissions()</methodname> method, since that method is never 2477 called in this case. 2478 </para> 2479 2480 <programlisting language="java"> 2481 // Help.java 2482 public class Help 2483 extends ChildItem 2484 implements Nameable 2485 2486 ... 2487 2488 SharedData getSharedParent() 2489 { 2490 return getData().getClient(); 2491 } 2492 </programlisting> 2493 2494 <bridgehead>Permissions granted by the base classes</bridgehead> 2495 2496 <variablelist> 2497 <varlistentry> 2498 <term><classname docapi="net.sf.basedb.core">BasicItem</classname></term> 2499 <listitem> 2500 <para> 2501 This class will grant or deny permissions as the are defined by the roles the logged 2502 in user is a member of. If a subclass extend directly from this class, it is common 2503 that the <methodname>initPermissions()</methodname> method needs to be overridden. 2504 </para> 2505 </listitem> 2506 </varlistentry> 2507 2508 <varlistentry> 2509 <term><classname docapi="net.sf.basedb.core">ChildItem</classname></term> 2510 <listitem> 2511 <para> 2512 This class grant <constant>READ</constant> permission if the logged in user has 2513 <constant>READ</constant> permission on the parent item, and <constant>CREATE</constant>, 2514 <constant>WRITE</constant> and <constant>DELETE</constant> 2515 permission if the logged in user has <constant>WRITE</constant> (configurable) 2516 permission on the parent item. 2517 </para> 2518 </listitem> 2519 </varlistentry> 2520 2521 <varlistentry> 2522 <term><classname docapi="net.sf.basedb.core">OwnedItem</classname></term> 2523 <listitem> 2524 <para> 2525 The owner of an item gets <constant>DELETE</constant>, <constant>SET_OWNER</constant> 2526 and <constant>SET_PERMISSION</constant> permissions. Delete permission also implies read, 2527 use and write permissions. Subclasses to this class usually doesn't have to overide the 2528 <methodname>initPermissions()</methodname> method. 2529 </para> 2530 </listitem> 2531 </varlistentry> 2532 2533 <varlistentry> 2534 <term><classname docapi="net.sf.basedb.core">SharedItem</classname></term> 2535 <listitem> 2536 <para> 2537 The logged in user get permissions as specified in the associated <classname docapi="net.sf.basedb.core">ItemKey</classname> 2538 and/or <classname docapi="net.sf.basedb.core">ProjectmKey</classname>. Subclasses to this class usually doesn't 2539 have to overide the <methodname>initPermissions()</methodname> method. 2540 </para> 2541 </listitem> 2542 </varlistentry> 2543 </variablelist> 2544 2545 <bridgehead>Checking for write permission in setter methods</bridgehead> 2546 2547 <para> 2548 An item class is required to check for <constant>WRITE</constant> permission in each 2549 method that modifies the state from a public method. Example: 2550 </para> 2551 2552 <programlisting language="java"> 2553 public void setName(String name) 2554 throws PermissionDeniedException 2555 { 2556 checkPermission(Permission.WRITE); 2557 // ... rest of code 2558 } 2559 </programlisting> 2560 2561 <warning> 2562 <para> 2563 If you forget this, an unauthorised user may be able to change the properties of an item. 2564 <constant>WRITE</constant> permissions are not checked in any other central place in the 2565 core code. Place the permission check on the first line in the method, before any data validation. 2566 This will make it easier to spot places where the permission check is forgotten. 2567 </para> 2568 </warning> 2569 2570 <bridgehead>Checking for use permission when creating associations</bridgehead> 2571 <para> 2572 An item class is required to check for <constant>USE</constant> permission on associated objects 2573 in each method that modifies the association from a public method. Example from the 2574 <classname docapi="net.sf.basedb.core">Protocol</classname> class: 2575 </para> 2576 2577 <programlisting language="java"> 2578 public void setFile(File file) 2579 throws PermissionDeniedException 2580 { 2581 checkPermission(Permission.WRITE); 2582 if (file != null) file.checkPermission(Permission.USE); 2583 getData().setFile(file == null ? null : file.getData()); 2584 } 2585 </programlisting> 2586 <warning> 2587 <para> 2588 If you forget this, an unauthorised user may be able to change the association of an item. 2589 <constant>USE</constant> permissions are not checked in any other central place in the core 2590 code. Place the permission check as early in the method as possible after it has been validated 2591 that the value isn't null. 2592 </para> 2593 </warning> 2594 2595 <bridgehead id="core_ref.rules.itemclass.getquery">Making sure the getQuery() method only returns items with read permission</bridgehead> 2596 2597 <para> 2598 This method can be one of the most complex ones of the entire class. The query it generates 2599 must always be compatible with the <methodname>initPermissions()</methodname> method. Ie. 2600 it must not return any items for which the <methodname>initPermissions()</methodname> method doesn't 2601 grant <constant>READ</constant> permission. And the other way around, if the <methodname>initPermissions()</methodname> 2602 method grants <constant>READ</constant> permission to and item, the query should be able to return it. 2603 The simplest case is if you doesn't override the <methodname>initPermissions()</methodname> method in 2604 such a way that it affects <constant>READ</constant> permissions. In this case you can just create a 2605 query and return it as it is. The query implementation will take care of the rest. 2606 </para> 2607 2608 <programlisting language="java"> 2609 <![CDATA[ 2610 // Client.java 2611 public static ItemQuery<Client> getQuery() 2612 { 2613 return new ItemQuery<Client>(Client.class); 2614 } 2615 ]]> 2616 </programlisting> 2617 2618 <para> 2619 A common case is when an item is the child of another item. Usually the parent is a 2620 <classname docapi="net.sf.basedb.core">Shareable</classname> item which means that we optimally 2621 should check the item and project keys on the parent when returning the children. But, this is a 2622 rather complex operation, so in this case we have choosen a different approach. The 2623 <methodname>getQuery()</methodname> method of child items must take a parameter of the parent type. 2624 The query can the safely return all children of that parent, since having a reference to the parent item, 2625 means that <constant>READ</constant> permission is granted. A null value for the parent is allowed, but 2626 then we fall back to check for role permissions only (with the help of a <classname 2627 docapi="net.sf.basedb.core">ChildFilter</classname> object). 2628 </para> 2629 <programlisting language="java"> 2630 <![CDATA[ 2631 // Help.java 2632 private static final QueryRuntimeFilter RUNTIME_FILTER = 2633 new QueryRuntimeFilterFactory.ChildFilter(Item.HELP, Item.CLIENT); 2634 2635 public static ItemQuery<Help> getQuery(Client client) 2636 { 2637 ItemQuery<Help> query = null; 2638 if (client != null) 2639 { 2640 query = new ItemQuery<Help>(Help.class, null); 2641 query.restrictPermanent( 2642 Restrictions.eq( 2643 Hql.property("client"), 2644 Hql.entity(client) 2645 ) 2646 ); 2647 } 2648 else 2649 { 2650 query = new ItemQuery<Help>(Help.class, RUNTIME_FILTER); 2651 } 2652 return query; 2653 } 2654 ]]> 2655 </programlisting> 2656 2657 <para> 2658 There are many other variants of the <methodname>getQuery()</methodname> method, for example all items 2659 having to with the authentication, <classname docapi="net.sf.basedb.core">User</classname>, 2660 <classname docapi="net.sf.basedb.core">Group</classname>, <classname docapi="net.sf.basedb.core">Role</classname>, etc. 2661 must check the logged in user's membership. We don't show any more examples here. Take a look in the source code 2662 if you want more information. You can also read <xref linkend="api_overview.query_api" /> for more examples. 2663 </para> 2664 2665 </sect3> 2666 2667 <sect3 id="core_ref.rules.itemclass.validation"> 2668 <title>Data validation</title> 2669 2670 <para> 2671 An item class must validate all data that is passed to it as parameters. There are three types of validation: 2672 </para> 2673 2674 <orderedlist> 2675 <listitem> 2676 <para> 2677 Validation of properties that are independent of other properties. For example, the length of a string or the value of number. 2678 </para> 2679 </listitem> 2680 <listitem> 2681 <para> 2682 Validation of properties that depends on other properties on the same object. For example, we have 2683 properties for the row and column counts, and then an array of linked objects for each position. 2684 </para> 2685 </listitem> 2686 <listitem> 2687 <para> 2688 Validation of properties that depends on the values of other objects. For example, the login of a 2689 user must be unique among all users. 2690 </para> 2691 </listitem> 2692 </orderedlist> 2693 2694 <para> 2695 For each of these types of validation we have choosen a strategy that is as simple as possible 2696 and doesn't force us to complex requirements on the code for objects. First, we may note that 2697 case 1 is very common, case 2 is very uncommon, and case 3 is just a bit more common than case 2. 2698 </para> 2699 2700 <bridgehead>Case 1 validation</bridgehead> 2701 2702 <para> 2703 For case 1 we choose to make the validation in the set method for each property. Example: 2704 </para> 2705 2706 <programlisting language="java"> 2707 public void setName(String name) 2708 throws InvalidDataException 2709 { 2710 checkPermission(Permission.WRITE); 2711 // Null is not allowed 2712 if (name == null) throw new InvalidUseOfNullException("name"); 2713 // The name must not be too long 2714 if (name.length > MAX_NAME_LENGTH) 2715 { 2716 throw new StringTooLongException("name", name, MAX_NAME_LENGTH); 2717 } 2718 getData().setName(name); 2719 } 2720 // Note! In this case we should actually use NameableUtil instead 2721 </programlisting> 2722 2723 <para> 2724 This will take care of all case 1 validation except that we cannot check properties that 2725 doesn't allow null values if the method never is called. To solve this problem we have 2726 two strategies: 2727 </para> 2728 2729 <itemizedlist> 2730 <listitem> 2731 <para> 2732 Provide a default value that is set in the constructor. For example the name 2733 of a new user can be initilised to "New user". 2734 </para> 2735 </listitem> 2736 <listitem> 2737 <para> 2738 Use constructor methods with parameters for required objects. 2739 </para> 2740 </listitem> 2741 </itemizedlist> 2742 2743 <para> 2744 Which strategy to use is decided from case to case. Failure to validate a property will usually 2745 result in a database exception, so no real harm is done, except that we don't want to show the 2746 ugly error messages to our users. The <classname docapi="net.sf.basedb.core">News</classname> 2747 class uses a mix of the two strategies: 2748 </para> 2749 2750 <programlisting language="java"> 2751 // News.java 2752 public static News getNew(DbControl dc, Date startDate, Date newsDate) 2753 { 2754 News n = dc.newItem(News.class); 2755 n.setName("New news"); 2756 n.setStartDate(startDate); 2757 n.setNewsDate(newsDate); 2758 n.getData().setEntryDate(new Date()); 2759 return n; 2760 } 2761 ... 2762 public void setStartDate(Date startDate) 2763 throws PermissionDeniedException, InvalidDataException 2764 { 2765 checkPermission(Permission.WRITE); 2766 getData().setStartDate(DateUtil.setNotNullDate(startDate, "startDate")); 2767 } 2768 ... 2769 </programlisting> 2770 2771 <bridgehead>Case 2 validation</bridgehead> 2772 2773 <para> 2774 This case requires interception of saves and updates and a call to the <methodname>validate()</methodname> 2775 method on the item. This automatically done on items which implements the <interfacename 2776 docapi="net.sf.basedb.core">Validatable</interfacename> interface. Internally this functionality is 2777 implemented by the <classname docapi="net.sf.basedb.core">DbControl</classname> class, which keeps a 2778 "commit queue" that holds all loaded items that implements the <interfacename 2779 docapi="net.sf.basedb.core">Validatable</interfacename> interface. When <methodname>DbControl.commit()</methodname> 2780 is called, the queue is iterated and the <methodname>validate()</methodname> method is called for each item. 2781 Here is another example from the <classname docapi="net.sf.basedb.core">News</classname> class which must 2782 validate that the three dates (startDate, newsData and endData) are in proper order: 2783 </para> 2784 <programlisting language="java"> 2785 <![CDATA[ 2786 // News.java 2787 void validate() 2788 throws InvalidDataException, BaseException 2789 { 2790 super.validate(); 2791 Date startDate = getData().getStartDate(); 2792 Date newsDate = getData().getNewsDate(); 2793 Date endDate = getData().getEndDate(); 2794 if (startDate.after(newsDate)) 2795 { 2796 throw new InvalidDataException("Invalid date. startDate is after newsDate."); 2797 } 2798 if (endDate != null && newsDate.after(endDate)) 2799 { 2800 throw new InvalidDataException("Invalid date. newsDate is after endDate."); 2801 } 2802 } 2803 ]]> 2804 </programlisting> 2805 2806 <bridgehead>Case 3 validation</bridgehead> 2807 <para> 2808 Usually, we do not bother with checking for this case, but delegates to the database 2809 to do the check. The reason that we do not bother to check for this case is that we can't 2810 be sure to succeed even if we first check the database. It is possible that during the time 2811 between our check and the actual insert or update, another transaction has already inserted 2812 another object into the database that violates the check. This is not perfect and the error 2813 messages are a bit ugly, but under the circumstances it is the best we can do. 2814 </para> 2815 </sect3> 2816 2817 <sect3 id="core_ref.rules.itemclass.transactions"> 2818 <title>Participating in transactions</title> 2819 2820 <para> 2821 Sometimes it is neccessary for an item to intercept certain events. For example, 2822 the <classname docapi="net.sf.basedb.core">File</classname> object needs to know 2823 if a transaction has been completed or rollbacked so it can clean up temporary 2824 files that have been used. We have created the <interfacename 2825 docapi="net.sf.basedb.core">Transactional</interfacename> interface, which is a tagging 2826 interface that tells the core to call certain methods on the item at certain events. 2827 The interface doesn't contain any methods, the item class needs to override methods 2828 from the <classname docapi="net.sf.basedb.core">BasicItem</classname> class. The following 2829 events/methods have been defined: 2830 </para> 2831 2832 <note> 2833 <para> 2834 The methods are always called for new items and items that are about to be 2835 deleted. It is only neccessary for an item to implement the <interfacename 2836 docapi="net.sf.basedb.core">Transactional</interfacename> interface if it 2837 needs to act on <constant>UPDATE</constant> events. 2838 </para> 2839 </note> 2840 2841 <variablelist> 2842 <varlistentry> 2843 <term>onBeforeCommit(Action)</term> 2844 <listitem> 2845 <para> 2846 This method is called before a commit is issued to Hibernate. It should be used by an 2847 item when it needs to update dependent objects before anything is written to the database. 2848 Note that nothing has been sent to the database yet and new items has not got an id when 2849 this method is called. If you override this method you must call <methodname>super.onBeforeCommit()</methodname> 2850 to allow the superclass to do whatever it needs to do. Here is an example from the 2851 <classname docapi="net.sf.basedb.core">OwnedItem</classname> class which sets the owner to the 2852 currently logged in user, if no owner has been explicitely specified: 2853 </para> 2854 2855 <programlisting language="java"> 2856 <![CDATA[ 2857 void onBeforeCommit(Transactional.Action action) 2858 throws NotLoggedInException, BaseException 2859 { 2860 super.onBeforeCommit(action); 2861 if (action == Transactional.Action.CREATE && getData().getOwner() == null) 2862 { 2863 org.hibernate.Session session = getDbControl().getHibernateSession(); 2864 int loggedInuserId = getSessionControl().getLoggedInUserId(); 2865 UserData owner = 2866 HibernateUtil.loadData(session, UserData.class, loggedInuserId); 2867 if (owner == null) throw new NotLoggedInException(); 2868 getData().setOwner(owner); 2869 } 2870 } 2871 ]]> 2872 </programlisting> 2873 </listitem> 2874 </varlistentry> 2875 2876 <varlistentry> 2877 <term>setProjectDefaults(Project)</term> 2878 <listitem> 2879 <para> 2880 This method is called before inserting new items into the database 2881 to allow items to propagate default values from the active project. 2882 The method is only called when a project is active. Subclasses should 2883 always call <methodname>super.setProjectDefaults()</methodname> 2884 and should only set default values that hasn't been explicitely set 2885 by client code (including <code>setFoo(null)</code> calls). 2886 </para> 2887 2888 <note> 2889 <para> 2890 With few exceptions a project can only hold <classname docapi="net.sf.basedb.core">ItemSubtype</classname> 2891 items as default values. This means that the item that is going to use the default 2892 value should implement the <interfacename docapi="net.sf.basedb.core">Subtypeable</interfacename> 2893 interface and list the other related item types in the <code>@SubtypableRelatedItems</code> 2894 annotation. 2895 </para> 2896 </note> 2897 2898 <programlisting language="java"> 2899 <![CDATA[ 2900 // DerivedBioAssaySet.java 2901 @Override 2902 @SubtypableRelatedItems({Item.PHYSICALBIOASSAY, Item.DERIVEDBIOASSAYSET, Item.SOFTWARE, Item.HARDWARE, Item.PROTOCOL}) 2903 public ItemSubtype getItemSubtype() 2904 { 2905 return getDbControl().getItem(ItemSubtype.class, getData().getItemSubtype()); 2906 } 2907 2908 /** 2909 Set protocol, hardware and software from project default settings. 2910 */ 2911 @Override 2912 void setProjectDefaults(Project activeProject) 2913 throws BaseException 2914 { 2915 super.setProjectDefaults(activeProject); 2916 if (!hasPermission(Permission.WRITE)) return; 2917 2918 DbControl dc = getDbControl(); 2919 if (!protocolHasBeenSet) 2920 { 2921 ProtocolData protocol = 2922 (ProtocolData)activeProject.findDefaultRelatedData(dc, this, Item.PROTOCOL, false); 2923 if (protocol != null) 2924 { 2925 getData().setProtocol(protocol); 2926 protocolHasBeenSet = true; 2927 } 2928 } 2929 if (!hardwareHasBeenSet) 2930 { 2931 HardwareData hardware = 2932 (HardwareData)activeProject.findDefaultRelatedData(dc, this, Item.HARDWARE, false); 2933 if (hardware != null) 2934 { 2935 getData().setHardware(hardware); 2936 hardwareHasBeenSet = true; 2937 } 2938 } 2939 if (!softwareHasBeenSet) 2940 { 2941 SoftwareData software = 2942 (SoftwareData)activeProject.findDefaultRelatedData(dc, this, Item.SOFTWARE, false); 2943 if (software != null) 2944 { 2945 getData().setSoftware(software); 2946 softwareHasBeenSet = true; 2947 } 2948 } 2949 } 2950 ]]> 2951 </programlisting> 2952 </listitem> 2953 </varlistentry> 2954 2955 <varlistentry> 2956 <term>onAfterInsert()</term> 2957 <listitem> 2958 <para> 2959 This method is called on all items directly after Hibernate has inserted 2960 it into the database. This method can be used in place of the 2961 <methodname>onBeforeCommit()</methodname> in case the id is needed. 2962 </para> 2963 </listitem> 2964 </varlistentry> 2965 2966 <varlistentry> 2967 <term>onAfterCommit(Action)</term> 2968 <listitem> 2969 <para> 2970 This method is called after a successful commit has been issued to Hibernate. It should be 2971 used by an item which needs to do additional processing. For example the 2972 <classname docapi="net.sf.basedb.core">File</classname> object may need to cleanup temporary files. 2973 This method should not use the database and it must not fail, since it is impossible to rollback 2974 anything that has already been committed to the database. If the method fails, it should log 2975 an exception with the <methodname>Application.log()</methodname> method. 2976 </para> 2977 </listitem> 2978 </varlistentry> 2979 2980 <varlistentry> 2981 <term>onRollback(Action)</term> 2982 <listitem> 2983 <para> 2984 This method is called after an unsuccessful commit has been issued to Hibernate. The same rules as 2985 for the <methodname>onAfterCommit()</methodname> method applies to this method. 2986 </para> 2987 </listitem> 2988 </varlistentry> 2989 </variablelist> 2990 2991 <para> 2992 Internally this functionality is implemented by the <classname docapi="net.sf.basedb.core">DbControl</classname> 2993 class, which keeps a "commit queue" that holds all new objects, all objects that are about to be deleted 2994 and all objects that implements the <interfacename docapi="net.sf.basedb.core">Transactional</interfacename> 2995 interface. When <methodname>DbControl.commit()</methodname> is called, the queue is iterated and 2996 <methodname>onBeforeCommit()</methodname> is called for each item, and then 2997 either <methodname>onAfterCommit()</methodname> or <methodname>onRollback()</methodname>. 2998 The <classname docapi="net.sf.basedb.core.Transactional">Action</classname> parameter is of an 2999 enumeration type which can hae three different values: 3000 </para> 3001 3002 3003 <itemizedlist> 3004 <listitem> 3005 <para> 3006 <constant>CREATE</constant>: This is a new item which is saved to the database for the first time. 3007 </para> 3008 </listitem> 3009 <listitem> 3010 <para> 3011 <constant>UPDATE</constant>: This is an existing item, which has been modified. 3012 </para> 3013 </listitem> 3014 <listitem> 3015 <para> 3016 <constant>DELETE</constant>: This is an existing item, which is now being deleted from the database 3017 </para> 3018 </listitem> 3019 </itemizedlist> 3020 3021 </sect3> 3022 3023 <sect3 id="core_ref.rules.itemclass.template"> 3024 <title>Template code for item classes</title> 3025 3026 <para> 3027 The <ulink url="../../examples/AnyItem.java.txt">AnyItem.java</ulink> and <ulink 3028 url="../../examples/AChildItem.java.txt">AChildItem.java</ulink> files 3029 contains two complete item classes with lots of template methods. Please copy and paste as much as you want 3030 from these, but do not forget to change the specific details. 3031 </para> 3032 3033 <bridgehead>Class declaration</bridgehead> 3034 3035 <para> 3036 An item class should extend one of the four classes: <classname docapi="net.sf.basedb.core">BasicItem</classname>, 3037 <classname docapi="net.sf.basedb.core">OwnedItem</classname>, <classname docapi="net.sf.basedb.core">SharedItem</classname> 3038 and <classname docapi="net.sf.basedb.core">CommonItem</classname>. Which one depends on what combination of 3039 interfaces are needed for that item. The most common situation is probably to extend the <classname 3040 docapi="net.sf.basedb.core">CommonItem</classname> class. Do not forget to include the GNU licence and 3041 copyright statement. Also note that the corresponding data layer class is specified as a 3042 generics parameter of the superclass. 3043 </para> 3044 3045 <programlisting language="java"> 3046 <![CDATA[ 3047 /* 3048 $Id $ 3049 3050 Copyright (C) 2011 Your name 3051 3052 This file is part of BASE - BioArray Software Environment. 3053 Available at http://base.thep.lu.se/ 3054 3055 BASE is free software; you can redistribute it and/or 3056 modify it under the terms of the GNU General Public License 3057 as published by the Free Software Foundation; either version 3 3058 of the License, or (at your option) any later version. 3059 3060 BASE is distributed in the hope that it will be useful, 3061 but WITHOUT ANY WARRANTY; without even the implied warranty of 3062 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 3063 GNU General Public License for more details. 3064 3065 You should have received a copy of the GNU General Public License 3066 along with BASE. If not, see <http://www.gnu.org/licenses/>. 3067 */ 3068 package net.sf.basedb.core; 3069 import net.sf.basedb.core.data.AnyData; 3070 /** 3071 This class is used to represent an AnyItem in BASE. 3072 3073 @author Your name 3074 @since 3.0 3075 @see AnyData 3076 @base.modified $Date$ 3077 */ 3078 public class AnyItem 3079 extends CommonItem<AnyData> 3080 { 3081 ... 3082 } 3083 ]]> 3084 </programlisting> 3085 3086 <bridgehead>Static methods and fields</bridgehead> 3087 3088 <variablelist> 3089 <varlistentry> 3090 <term>getNew(DbControl)</term> 3091 <listitem> 3092 <para> 3093 This method is used to create a new item. The new item must be created by calling the 3094 <methodname>DbControl.newItem()</methodname>. 3095 </para> 3096 3097 <programlisting language="java"> 3098 <![CDATA[ 3099 /** 3100 Create a new <code>AnyItem</code> item. 3101 3102 @param dc The <code>DbControl</code> which will be used for 3103 permission checking and database access 3104 @return The new <code>AnyItem</code> item 3105 @throws BaseException If there is an error 3106 */ 3107 public static AnyItem getNew(DbControl dc) 3108 throws BaseException 3109 { 3110 AnyItem a = dc.newItem(AnyItem.class); 3111 a.setName("New any item"); 3112 return a; 3113 } 3114 ]]> 3115 </programlisting> 3116 <para> 3117 The method must initialise all not-null properties to a sensible default values or it may take values as parameters: 3118 </para> 3119 <programlisting language="java"> 3120 <![CDATA[ 3121 // User.java 3122 public static User getNew(DbControl dc, String login, String password) 3123 throws BaseException 3124 { 3125 User u = dc.newItem(User.class); 3126 u.setName("New user"); 3127 u.setLogin(login); 3128 u.setPassword(password); 3129 int defaultQuotaId = SystemItems.getId(Quota.DEFAULT); 3130 org.hibernate.Session session = dc.getHibernateSession(); 3131 QuotaData defaultQuota = 3132 HibernateUtil.loadData(session, QuotaData.class, defaultQuotaId); 3133 u.getData().setQuota(defaultQuota); 3134 return u; 3135 } 3136 ]]> 3137 </programlisting> 3138 <para> 3139 When the default value is an association to another item, use the data object 3140 (<classname docapi="net.sf.basedb.core.data">QuotaData</classname>) not the item object 3141 (<classname docapi="net.sf.basedb.core">Quota</classname>) to create the association. 3142 The reason for this is that the logged in user may not have read permission to the default 3143 object. Ie. The logged in user may have permission to create users, but not permission to 3144 read quota. 3145 </para> 3146 3147 </listitem> 3148 </varlistentry> 3149 3150 <varlistentry> 3151 <term>getById(DbControl, int)</term> 3152 <listitem> 3153 <para> 3154 This method is used to load an item from the database when the id of that item is known. 3155 Use the <methodname>DbControl.loadItem()</methodname> method to load the item. If the item 3156 is not found an <classname docapi="net.sf.basedb.core">ItemNotFoundException</classname> should 3157 be thrown. 3158 </para> 3159 3160 <programlisting language="java"> 3161 <![CDATA[ 3162 /** 3163 Get an <code>AnyItem</code> item when you know the id. 3164 3165 @param dc The <code>DbControl</code> which will be used for 3166 permission checking and database access. 3167 @param id The id of the item to load 3168 @return The <code>AnyItem</code> item 3169 @throws ItemNotFoundException If an item with the specified 3170 id is not found 3171 @throws PermissionDeniedException If the logged in user doesn't 3172 have read permission to the item 3173 @throws BaseException If there is another error 3174 */ 3175 public static AnyItem getById(DbControl dc, int id) 3176 throws ItemNotFoundException, PermissionDeniedException, BaseException 3177 { 3178 AnyItem a = dc.loadItem(AnyItem.class, id); 3179 if (a == null) throw new ItemNotFoundException("AnyItem[id="+id+"]"); 3180 return a; 3181 } 3182 ]]> 3183 </programlisting> 3184 3185 </listitem> 3186 </varlistentry> 3187 3188 <varlistentry> 3189 <term>getQuery()</term> 3190 <listitem> 3191 <para> 3192 See <xref linkend="core_ref.rules.itemclass.getquery" />. 3193 </para> 3194 </listitem> 3195 </varlistentry> 3196 </variablelist> 3197 3198 <bridgehead>Constructors</bridgehead> 3199 3200 <para> 3201 Each item class needs only one constructor, which takes an object of the corresponding 3202 data class as a parameter. The constructor should never be invoked directly. Use the 3203 <methodname>DbControl.newItem()</methodname> method. 3204 </para> 3205 3206 <programlisting language="java"> 3207 <![CDATA[ 3208 AnyItem(AnyData anyData) 3209 { 3210 super(anyData); 3211 } 3212 ]]> 3213 </programlisting> 3214 3215 <bridgehead>Core methods</bridgehead> 3216 3217 <variablelist> 3218 <varlistentry> 3219 <term>isUsed()</term> 3220 <listitem> 3221 <para> 3222 This method is defined by the <classname docapi="net.sf.basedb.core">BasicItem</classname> class and 3223 is called whenever we need to know if there are other items referencing the current item. The main 3224 use case is to let client applications know if it is safe to delete an object or not. 3225 The default implementation checks <classname docapi="net.sf.basedb.core">AnyToAny</classname> 3226 links between items. A subclass must override this method if it can be referenced by other items. 3227 A subclass should always call <methodname>super.isUsed()</methodname> as a last check if it is not 3228 used by any other item. The method should check if it is beeing used (referenced by) some other item. 3229 For example, a <classname docapi="net.sf.basedb.core">Tag</classname> is used if there is an 3230 <classname docapi="net.sf.basedb.core">Extract</classname> with that tag. The simplest way to check 3231 if the item is used is to use a predefined query that counts the number of references. 3232 </para> 3233 3234 <programlisting language="java"> 3235 <![CDATA[ 3236 /** 3237 Check if: 3238 <ul> 3239 <li>Some {@link Extract}:s are marked with this tag 3240 </ul> 3241 */ 3242 public boolean isUsed() 3243 throws BaseException 3244 { 3245 org.hibernate.Session session = getDbControl().getHibernateSession(); 3246 org.hibernate.Query query = HibernateUtil.getPredefinedQuery(session, 3247 "GET_EXTRACTS_FOR_TAG", "count(*)"); 3248 /* 3249 SELECT {1} 3250 FROM ExtractData ext 3251 WHERE ext.tag = :tag 3252 */ 3253 query.setEntity("tag", this.getData()); 3254 boolean used = HibernateUtil.loadData(Long.class, query) > 0; 3255 return used || super.isUsed(); 3256 } 3257 ]]> 3258 </programlisting> 3259 3260 <para> 3261 Sometimes it may be harder to decide what counts as using an item or not. Some examples: 3262 </para> 3263 3264 <itemizedlist> 3265 <listitem> 3266 <para> 3267 An event for a sample does not count as using the sample, since they hava a 3268 parent-child relationship. Ie. deleting the sample will also delete all events 3269 associated with it. On the other hand, the protocol registered for the event counts 3270 as using the protocol, because deleting the protocol should not delete all events. 3271 </para> 3272 </listitem> 3273 <listitem> 3274 <para> 3275 As a general rule, if one item is used by a second item, then the second item cannot 3276 be used by the first. It could lead to situations where it would be impossible to 3277 delete either one of them. 3278 </para> 3279 </listitem> 3280 </itemizedlist> 3281 </listitem> 3282 </varlistentry> 3283 3284 <varlistentry> 3285 <term>getUsingItems()</term> 3286 <listitem> 3287 <para> 3288 Find all items that are referencing this one. This method is related to the 3289 <methodname>isUsed()</methodname> method and is defined in the <classname docapi="net.sf.basedb.core">BasicItem</classname> 3290 class. The default implementation load all items linked via an <classname docapi="net.sf.basedb.core">AnyToAny</classname> 3291 link that has the <code>usingTo</code> flag set to true. A subclass must override this method if it can 3292 be referenced to be used by other items. A subclass should always call <methodname>super.getUsingItems()</methodname> 3293 first and then add extra items to the Set returned by that call. For example, a <classname docapi="net.sf.basedb.core">Tag</classname> 3294 should load all <classname docapi="net.sf.basedb.core">Extract</classname>:s with that tag. 3295 </para> 3296 <programlisting language="java"> 3297 <![CDATA[ 3298 /** 3299 Get all: 3300 <ul> 3301 <li>{@link Extract}:s marked with this tag 3302 <ul> 3303 */ 3304 @Override 3305 public Set<ItemProxy> getUsingItems() 3306 { 3307 Set<ItemProxy> using = super.getUsingItems(); 3308 org.hibernate.Session session = getDbControl().getHibernateSession(); 3309 3310 // Extracts 3311 org.hibernate.Query query = HibernateUtil.getPredefinedQuery(session, 3312 "GET_EXTRACTS_FOR_TAG", "ext.id"); 3313 /* 3314 SELECT {1} 3315 FROM ExtractData ext 3316 WHERE ext.tag = :tag 3317 */ 3318 query.setEntity("tag", this.getData()); 3319 addUsingItems(using, Item.EXTRACT, query); 3320 return using; 3321 } 3322 ]]> 3323 </programlisting> 3324 </listitem> 3325 </varlistentry> 3326 3327 <varlistentry> 3328 <term>initPermissions(int, int)</term> 3329 <listitem> 3330 <para> 3331 See <xref linkend="core_ref.rules.itemclass.initpermissions" />. 3332 </para> 3333 </listitem> 3334 </varlistentry> 3335 3336 <varlistentry> 3337 <term>validate() </term> 3338 <listitem> 3339 <para> 3340 See <xref linkend="core_ref.rules.itemclass.validation" />. 3341 </para> 3342 </listitem> 3343 </varlistentry> 3344 3345 <varlistentry> 3346 <term>onBeforeCommit(Action)</term> 3347 <term>setProjectDefaults(Project)</term> 3348 <term>onAfterInsert()</term> 3349 <term>onAfterCommit(Action) </term> 3350 <term>onRollback(Action)</term> 3351 <listitem> 3352 <para> 3353 See <xref linkend="core_ref.rules.itemclass.transactions"/>. 3354 </para> 3355 </listitem> 3356 </varlistentry> 3357 </variablelist> 3358 3359 <bridgehead>Getter and setter methods</bridgehead> 3360 3361 <para> 3362 The get methods for basic property types are usually very simple. All that is needed is to 3363 return the value. Be aware of date values though, they are mutable and must be copied. 3364 </para> 3365 3366 <programlisting language="java"> 3367 <![CDATA[ 3368 /** 3369 Get the value of the string property. 3370 */ 3371 public String getStringProperty() 3372 { 3373 return getData().getStringProperty(); 3374 } 3375 3376 /** 3377 Get the value of the int property. 3378 */ 3379 public int getIntProperty() 3380 { 3381 return getData().getIntProperty(); 3382 } 3383 3384 /** 3385 Get the value of the boolean property. 3386 */ 3387 public boolean isBooleanProperty() 3388 { 3389 return getData().isBooleanProperty(); 3390 } 3391 3392 /** 3393 Get the value of the date property. 3394 @return A date object or null if unknown 3395 */ 3396 public Date getDateProperty() 3397 { 3398 return DateUtil.copy(getData().getDateProperty()); 3399 } 3400 ]]> 3401 </programlisting> 3402 3403 <para> 3404 The set methods must always check for <constant>WRITE</constant> permission and validate 3405 the parameters. There are plenty of utility method to help with this. 3406 </para> 3407 3408 <programlisting language="java"> 3409 <![CDATA[ 3410 /** 3411 The maximum length of the string property. Check the length 3412 agains this value before calling {@link #setStringProperty(String)} 3413 to avoid exceptions. 3414 */ 3415 public static final int MAX_STRINGPROPERTY_LENGTH = 3416 AnyData.MAX_STRINGPROPERTY_LENGTH; 3417 3418 /** 3419 Set the value of the string property. Null values are not 3420 allowed and the length must be shorter than 3421 {@link #MAX_STRINGPROPERTY_LENGTH}. 3422 @param value The new value 3423 @throws PermissionDeniedException If the logged in user 3424 doesn't have write permission 3425 @throws InvalidDataException If the value is null or too long 3426 */ 3427 public void setStringProperty(String value) 3428 throws PermissionDeniedException, InvalidDataException 3429 { 3430 checkPermission(Permission.WRITE); 3431 getData.setStringProperty( 3432 StringUtil.setNotNullString(value, "stringProperty", MAX_STRINGPROPERTY_LENGTH) 3433 ); 3434 } 3435 3436 /** 3437 Set the value of the int property. The value mustn't be less than 3438 zero. 3439 @param value The new value 3440 @throws PermissionDeniedException If the logged in user 3441 doesn't have write permission 3442 @throws InvalidDataException If the value is less than zero 3443 */ 3444 3445 public void setIntProperty(int value) 3446 throws PermissionDeniedException, InvalidDataException 3447 { 3448 checkPermission(Permission.WRITE); 3449 getData.setIntProperty( 3450 IntegerUtil.checkMin(value, "intProperty", 0) 3451 ); 3452 } 3453 3454 /** 3455 Set the value of the boolean property. 3456 @param value The new value 3457 @throws PermissionDeniedException If the logged in user 3458 doesn't have write permission 3459 */ 3460 public void setBooleanProperty(boolean value) 3461 throws PermissionDeniedException 3462 { 3463 checkPermission(Permission.WRITE); 3464 getData.setBooleanProperty(value); 3465 } 3466 3467 /** 3468 Set the value of the date property. Null values are allowed. 3469 @param value The new value 3470 @throws PermissionDeniedException If the logged in user 3471 doesn't have write permission 3472 */ 3473 public void setDateProperty(Date value) 3474 throws PermissionDeniedException 3475 { 3476 checkPermission(Permission.WRITE); 3477 getData().setDateProperty(DateUtil.setNullableDate(value, "dateProperty")); 3478 } 3479 ]]> 3480 </programlisting> 3481 3482 <bridgehead>Many-to-one associations</bridgehead> 3483 3484 <para> 3485 Many-to-one associations require sligthly more work. First of all, the item must be connected to a 3486 <classname docapi="net.sf.basedb.core">DbControl</classname> since it is used to load the information 3487 from the database and crete the new item object. Secondly, we must make sure to check for use 3488 permission on the referenced object in the setter method. 3489 </para> 3490 3491 <programlisting language="java"> 3492 <![CDATA[ 3493 /** 3494 Get the associated other item. 3495 @return The OtherItem item 3496 @throws PermissionDeniedException If the logged in user 3497 doesn't have read permission 3498 @throws BaseException If there is another error 3499 */ 3500 public OtherItem getOtherItem() 3501 throws PermissionDeniedException, BaseException 3502 { 3503 return getDbControl().getItem(OtherItem.class, getData().getOtherItem()); 3504 } 3505 3506 /** 3507 Set the associated item. Null is not allowed. 3508 @param other The other item 3509 @throws PermissionDeniedException If the logged in user 3510 doesn't have write permission 3511 @throws InvalidDataException If the other item is null 3512 @throws BaseException If there is another error 3513 */ 3514 public void setOtherItem(OtherItem other) 3515 throws PermissionDeniedException, InvalidDataException, BaseException 3516 { 3517 checkPermission(Permission.WRITE); 3518 if (otherItem == null) throw new InvalidUseOfNullException("otherItem"); 3519 getData().setOtherItem(otherItem.getData()); 3520 } 3521 ]]> 3522 </programlisting> 3523 3524 <bridgehead>One-to-many and many-to-many associations</bridgehead> 3525 <para> 3526 If the association is a one-to-many or many-to-many it becomes a little more complicated again. 3527 There are many types of such associations and how they are handled usually depends on if the 3528 are set:s, map:s, list:s or any other type of collections. In all cases we need methods for adding 3529 and removing items, and a method that returns a <interfacename docapi="net.sf.basedb.core.query">Query</interfacename> 3530 that can list all associated items. The first example if for parent/child relationship, which is a one-to-many 3531 association where the children are mapped as a set. 3532 </para> 3533 3534 <programlisting language="java"> 3535 <![CDATA[ 3536 /** 3537 Create a child item for this any item. 3538 @return The new AChildItem object 3539 @throws PermissionDeniedException If the logged in user doesn't have 3540 write permission 3541 @throws BaseException If there is another error 3542 */ 3543 public AChildItem newChildItem() 3544 throws PermissionDeniedException, BaseException 3545 { 3546 checkPermission(Permission.WRITE); 3547 return AChildItem.getNew(getDbControl(), this); 3548 } 3549 3550 /** 3551 Get a query that will return all child items for this any item. 3552 @return A {@link Query} object 3553 */ 3554 public ItemQuery<AChildItem> getChildItems() 3555 { 3556 return AChildItem.getQuery(this); 3557 } 3558 ]]> 3559 </programlisting> 3560 <para> 3561 The second example is for the many-to-many associations between users and roles, which is also mapped as a set. 3562 </para> 3563 <programlisting language="java"> 3564 <![CDATA[ 3565 // Role.java 3566 /** 3567 Add a user to this role. 3568 @param user The user to add 3569 @throws PermissionDeniedException If the logged in user doesn't 3570 have write permission for the role and 3571 use permission for the user 3572 @throws InvalidDataException If the user is null 3573 */ 3574 public void addUser(User user) 3575 throws PermissionDeniedException, InvalidDataException 3576 { 3577 checkPermission(Permission.WRITE); 3578 if (user == null) throw new InvalidUseOfNullException("user"); 3579 user.checkPermission(Permission.USE); 3580 getData().getUsers().add(user.getData()); 3581 } 3582 3583 /** 3584 Remove a user from this role. 3585 @param user The user to remove 3586 @throws PermissionDeniedException If the logged in user doesn't 3587 have write permission for the role and 3588 use permission for the user 3589 @throws InvalidDataException If the user is null 3590 */ 3591 public void removeUser(User user) 3592 throws PermissionDeniedException, InvalidDataException 3593 { 3594 checkPermission(Permission.WRITE); 3595 if (user == null) throw new InvalidUseOfNullException("user"); 3596 user.checkPermission(Permission.USE); 3597 getData().getUsers().remove(user.getData()); 3598 } 3599 3600 /** 3601 Check if the given user is member of this role or not. 3602 @param user The user to check 3603 @return TRUE if the user is member, FALSE otherwise 3604 */ 3605 public boolean isMember(User user) 3606 { 3607 return getData().getUsers().contains(user.getData()); 3608 } 3609 3610 /** 3611 Get a query that returns the users that 3612 are members of this role. This query excludes users that the logged 3613 in user doesn't have permission to read. 3614 @see User#getQuery() 3615 */ 3616 public ItemQuery<User> getUsers() 3617 { 3618 ItemQuery<User> query = User.getQuery(); 3619 query.joinPermanent( 3620 Hql.innerJoin("roles", Item.ROLE.getAlias()) 3621 ); 3622 query.restrictPermanent( 3623 Restrictions.eq( 3624 Hql.alias(Item.ROLE.getAlias()), 3625 Hql.entity(this) 3626 ) 3627 ); 3628 return query; 3629 } 3630 3631 // User.java 3632 /** 3633 Get a query that returns the roles where this user is a 3634 member. The query excludes roles that the logged in user doesn't have 3635 permission to read. 3636 @see Role#getQuery() 3637 */ 3638 public ItemQuery<Role> getRoles() 3639 { 3640 ItemQuery<Role> query = Role.getQuery(); 3641 query.joinPermanent( 3642 Hql.innerJoin("users", Item.USER.getAlias()) 3643 ); 3644 query.restrictPermanent( 3645 Restrictions.eq( 3646 Hql.alias(Item.USER.getAlias()), 3647 Hql.entity(this) 3648 ) 3649 ); 3650 return query; 3651 } 3652 ]]> 3653 </programlisting> 3654 <para> 3655 Note that we have a query method in both classes, but the association can only be changed from the 3656 <classname docapi="net.sf.based.core">Role</classname>. We recommend that modifier methods are put 3657 in one of the classes only. The last example is the many-to-many relation between projects and users 3658 which is a map to the permission for the user in the project. 3659 </para> 3660 3661 <programlisting language="java"> 3662 <![CDATA[ 3663 // Project.java 3664 /** 3665 Grant a user permissions to this project. Use an empty set 3666 or null to remove the user from this project. 3667 3668 @param user The user 3669 @param permissions The permissions to grant, or null to revoke all permissions 3670 @throws PermissionDeniedException If the logged in user doesn't have 3671 write permission for the project 3672 @throws InvalidDataException If the user is null 3673 @see Permission 3674 */ 3675 public void setPermissions(User user, Set<Permission> permissions) 3676 throws PermissionDeniedException, InvalidDataException 3677 { 3678 checkPermission(Permission.WRITE); 3679 if (user == null) throw new InvalidUseOfNullException("user"); 3680 if (permissions == null || permissions.isEmpty()) 3681 { 3682 getData().getUsers().remove(user.getData()); 3683 } 3684 else 3685 { 3686 getData().getUsers().put(user.getData(), Permission.grant(permissions)); 3687 } 3688 } 3689 3690 /** 3691 Get the permissions for a user in this project. 3692 @param user The user for which we want to get the permission 3693 @return A set containing the granted permissions, or an 3694 empty set if no permissions have been granted 3695 @throws InvalidDataException If the user is null 3696 @see Permission 3697 */ 3698 public Set<Permission> getPermissions(User user) 3699 throws InvalidDataException 3700 { 3701 if (user == null) throw new InvalidUseOfNullException("user"); 3702 return Permission.fromInt(getData().getUsers().get(user.getData())); 3703 } 3704 3705 /** 3706 Get a query that returns the users that 3707 are members of this project. This query excludes users that the logged 3708 in user doesn't have permission to read. 3709 @see User#getQuery() 3710 */ 3711 public ItemQuery<User> getUsers() 3712 { 3713 ItemQuery<User> query = User.getQuery(); 3714 query.joinPermanent( 3715 Hql.innerJoin("projects", Item.PROJECT.getAlias()) 3716 ); 3717 query.restrictPermanent( 3718 Restrictions.eq( 3719 Hql.alias(Item.PROJECT.getAlias()), 3720 Hql.entity(this) 3721 ) 3722 ); 3723 return query; 3724 } 3725 3726 // User.java 3727 /** 3728 Get a query that returns the projects where this user is a 3729 member. The query excludes projects that the logged in user doesn't have 3730 permission to read. The query doesn't include projects where this user is 3731 the owner. 3732 @see Project#getQuery() 3733 */ 3734 public ItemQuery<Project> getProjects() 3735 { 3736 ItemQuery<Project> query = Project.getQuery(); 3737 query.joinPermanent( 3738 Hql.innerJoin("users", Item.USER.getAlias()) 3739 ); 3740 query.restrictPermanent( 3741 Restrictions.eq( 3742 Hql.index(Item.USER.getAlias(), null), 3743 Hql.entity(this) 3744 ) 3745 ); 3746 return query; 3747 } 3748 ]]> 3749 </programlisting> 3750 <para> 3751 As you can see from these examples, the code is very different depending on the type of 3752 association. We don't give any more examples here, but if you are unsure you should look 3753 in the source code to get more inspiration. 3754 </para> 3755 </sect3> 2343 3756 </sect2> 3757 2344 3758 <sect2 id="core_ref.rules.batchclass"> 2345 3759 <title>Batch-class rules</title> … … 2347 3761 TODO 2348 3762 </para> 2349 </sect2> 3763 </sect2> 3764 2350 3765 <sect2 id="core_ref.rules.testclass"> 2351 3766 <title>Test-class rules</title> … … 2353 3768 TODO 2354 3769 </para> 2355 </sect2> 3770 </sect2> 2356 3771 </sect1> 2357 3772
Note: See TracChangeset
for help on using the changeset viewer.