View Javadoc

    1   /* Copyright 2004, 2005 Acegi Technology Pty Limited
    2    *
    3    * Licensed under the Apache License, Version 2.0 (the "License");
    4    * you may not use this file except in compliance with the License.
    5    * You may obtain a copy of the License at
    6    *
    7    *     http://www.apache.org/licenses/LICENSE-2.0
    8    *
    9    * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15
    16  package org.acegisecurity.acl.basic.jdbc;
    17
    18  import org.acegisecurity.acl.basic.AclObjectIdentity;
    19  import org.acegisecurity.acl.basic.BasicAclDao;
    20  import org.acegisecurity.acl.basic.BasicAclEntry;
    21  import org.acegisecurity.acl.basic.NamedEntityObjectIdentity;
    22
    23  import org.apache.commons.logging.Log;
    24  import org.apache.commons.logging.LogFactory;
    25
    26  import org.springframework.context.ApplicationContextException;
    27
    28  import org.springframework.jdbc.core.SqlParameter;
    29  import org.springframework.jdbc.core.support.JdbcDaoSupport;
    30  import org.springframework.jdbc.object.MappingSqlQuery;
    31
    32  import org.springframework.util.Assert;
    33
    34  import java.sql.ResultSet;
    35  import java.sql.SQLException;
    36  import java.sql.Types;
    37
    38  import java.util.List;
    39  import java.util.Vector;
    40
    41  import javax.sql.DataSource;
    42
    43
    44  /***
    45   * <p>
    46   * Retrieves ACL details from a JDBC location.
    47   * </p>
    48   *
    49   * <p>
    50   * A default database structure is assumed. This may be overridden by setting
    51   * the default query strings to use. If this does not provide enough
    52   * flexibility, another strategy would be to subclass this class and override
    53   * the {@link MappingSqlQuery} instance used, via the {@link
    54   * #initMappingSqlQueries()} extension point.
    55   * </p>
    56   */
    57  public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
    58      //~ Static fields/initializers =============================================
    59
    60      public static final String RECIPIENT_USED_FOR_INHERITENCE_MARKER = "___INHERITENCE_MARKER_ONLY___";
    61      public static final String DEF_ACLS_BY_OBJECT_IDENTITY_QUERY = "SELECT RECIPIENT, MASK FROM acl_permission WHERE acl_object_identity = ?";
    62      public static final String DEF_OBJECT_PROPERTIES_QUERY = "SELECT CHILD.ID, CHILD.OBJECT_IDENTITY, CHILD.ACL_CLASS, PARENT.OBJECT_IDENTITY as PARENT_OBJECT_IDENTITY FROM acl_object_identity as CHILD LEFT OUTER JOIN acl_object_identity as PARENT ON CHILD.parent_object=PARENT.id WHERE CHILD.object_identity = ?";
    63      private static final Log logger = LogFactory.getLog(JdbcDaoImpl.class);
    64
    65      //~ Instance fields ========================================================
    66
    67      protected MappingSqlQuery aclsByObjectIdentity;
    68      protected MappingSqlQuery objectProperties;
    69      private String aclsByObjectIdentityQuery;
    70      private String objectPropertiesQuery;
    71
    72      //~ Constructors ===========================================================
    73
    74      public JdbcDaoImpl() {
    75          aclsByObjectIdentityQuery = DEF_ACLS_BY_OBJECT_IDENTITY_QUERY;
    76          objectPropertiesQuery = DEF_OBJECT_PROPERTIES_QUERY;
    77      }
    78
    79      //~ Methods ================================================================
    80
    81      /***
    82       * Responsible for covering a <code>AclObjectIdentity</code> to a
    83       * <code>String</code> that can be located in the RDBMS.
    84       *
    85       * @param aclObjectIdentity to locate
    86       *
    87       * @return the object identity as a <code>String</code>
    88       */
    89      protected String convertAclObjectIdentityToString(
    90          AclObjectIdentity aclObjectIdentity) {
    91          // Ensure we can process this type of AclObjectIdentity
    92          Assert.isInstanceOf(NamedEntityObjectIdentity.class, aclObjectIdentity,
    93              "Only aclObjectIdentity of type NamedEntityObjectIdentity supported (was passed: "
    94              + aclObjectIdentity + ")");
    95
    96          NamedEntityObjectIdentity neoi = (NamedEntityObjectIdentity) aclObjectIdentity;
    97
    98          // Compose the String we expect to find in the RDBMS
    99          return neoi.getClassname() + ":" + neoi.getId();
    100     }
    101
    102     /***
    103      * Constructs an individual <code>BasicAclEntry</code> from the passed
    104      * <code>AclDetailsHolder</code>s.
    105      *
    106      * <P>
    107      * Guarantees to never return <code>null</code> (exceptions are thrown in
    108      * the event of any issues).
    109      * </p>
    110      *
    111      * @param propertiesInformation mandatory information about which instance
    112      *        to create, the object identity, and the parent object identity
    113      *        (<code>null</code> or empty <code>String</code>s prohibited for
    114      *        <code>aclClass</code> and <code>aclObjectIdentity</code>
    115      * @param aclInformation optional information about the individual ACL
    116      *        record (if <code>null</code> only an "inheritence marker"
    117      *        instance is returned; if not <code>null</code>, it is prohibited
    118      *        to present <code>null</code> or an empty <code>String</code> for
    119      *        <code>recipient</code>)
    120      *
    121      * @return a fully populated instance suitable for use by external objects
    122      *
    123      * @throws IllegalArgumentException if the indicated ACL class could not be
    124      *         created
    125      */
    126     private BasicAclEntry createBasicAclEntry(
    127         AclDetailsHolder propertiesInformation, AclDetailsHolder aclInformation) {
    128         BasicAclEntry entry;
    129
    130         try {
    131             entry = (BasicAclEntry) propertiesInformation.getAclClass()
    132                                                          .newInstance();
    133         } catch (InstantiationException ie) {
    134             throw new IllegalArgumentException(ie.getMessage());
    135         } catch (IllegalAccessException iae) {
    136             throw new IllegalArgumentException(iae.getMessage());
    137         }
    138
    139         entry.setAclObjectIdentity(propertiesInformation.getAclObjectIdentity());
    140         entry.setAclObjectParentIdentity(propertiesInformation
    141                 .getAclObjectParentIdentity());
    142
    143             if (aclInformation == null) {
    144                 // this is an inheritence marker instance only
    145                 entry.setMask(0);
    146                 entry.setRecipient(RECIPIENT_USED_FOR_INHERITENCE_MARKER);
    147             } else {
    148                 // this is an individual ACL entry
    149                 entry.setMask(aclInformation.getMask());
    150                 entry.setRecipient(aclInformation.getRecipient());
    151             }
    152
    153             return entry;
    154         }
    155
    156         /***
    157          * Returns the ACLs associated with the requested
    158          * <code>AclObjectIdentity</code>.
    159          *
    160          * <P>
    161          * The {@link BasicAclEntry}s returned by this method will have
    162          * <code>String</code>-based recipients. This will not be a problem if
    163          * you are using the
    164          * <code>GrantedAuthorityEffectiveAclsResolver</code>, which is the
    165          * default configured against <code>BasicAclProvider</code>.
    166          * </p>
    167          *
    168          * <P>
    169          * This method will only return ACLs for requests where the
    170          * <code>AclObjectIdentity</code> is of type {@link
    171          * NamedEntityObjectIdentity}. Of course, you can subclass or replace
    172          * this class and support your own custom
    173          * <code>AclObjectIdentity</code> types.
    174          * </p>
    175          *
    176          * @param aclObjectIdentity for which ACL information is required
    177          *        (cannot be <code>null</code> and must be an instance of
    178          *        <code>NamedEntityObjectIdentity</code>)
    179          *
    180          * @return the ACLs that apply (without any <code>null</code>s inside
    181          *         the array), or <code>null</code> if not found or if an
    182          *         incompatible <code>AclObjectIdentity</code> was requested
    183          */
    184         public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity) {
    185             String aclObjectIdentityString;
    186
    187             try {
    188                 aclObjectIdentityString = convertAclObjectIdentityToString(aclObjectIdentity);
    189             } catch (IllegalArgumentException unsupported) {
    190                 return null; // pursuant to contract described in JavaDocs above
    191             }
    192
    193             // Lookup the object's main properties from the RDBMS (guaranteed no nulls)
    194             List objects = objectProperties.execute(aclObjectIdentityString);
    195
    196             if (objects.size() == 0) {
    197                 // this is an unknown object identity string
    198                 return null;
    199             }
    200
    201             // Cast to an object properties holder (there should only be one record)
    202             AclDetailsHolder propertiesInformation = (AclDetailsHolder) objects
    203                 .get(0);
    204
    205             // Lookup the object's ACLs from RDBMS (guaranteed no nulls)
    206             List acls = aclsByObjectIdentity.execute(propertiesInformation
    207                         .getForeignKeyId());
    208
    209                 if (acls.size() == 0) {
    210                     // return merely an inheritence marker (as we know about the object but it has no related ACLs)
    211                     return new BasicAclEntry[] {createBasicAclEntry(propertiesInformation,
    212                             null)};
    213                 } else {
    214                     // return the individual ACL instances
    215                     AclDetailsHolder[] aclHolders = (AclDetailsHolder[]) acls
    216                             .toArray(new AclDetailsHolder[] {});
    217                         List toReturnAcls = new Vector();
    218
    219                         for (int i = 0; i < aclHolders.length; i++) {
    220                             toReturnAcls.add(createBasicAclEntry(
    221                                     propertiesInformation, aclHolders[i]));
    222                         }
    223
    224                         return (BasicAclEntry[]) toReturnAcls.toArray(new BasicAclEntry[] {});
    225                     }
    226                 }
    227
    228                 public MappingSqlQuery getAclsByObjectIdentity() {
    229                     return aclsByObjectIdentity;
    230                 }
    231
    232                 public String getAclsByObjectIdentityQuery() {
    233                     return aclsByObjectIdentityQuery;
    234                 }
    235
    236                 public String getObjectPropertiesQuery() {
    237                     return objectPropertiesQuery;
    238                 }
    239
    240                 protected void initDao() throws ApplicationContextException {
    241                     initMappingSqlQueries();
    242                 }
    243
    244                 /***
    245                  * Extension point to allow other MappingSqlQuery objects to be
    246                  * substituted in a subclass
    247                  */
    248                 protected void initMappingSqlQueries() {
    249                     setAclsByObjectIdentity(new AclsByObjectIdentityMapping(
    250                             getDataSource()));
    251                     setObjectProperties(new ObjectPropertiesMapping(
    252                             getDataSource()));
    253                 }
    254
    255                 public void setAclsByObjectIdentity(
    256                     MappingSqlQuery aclsByObjectIdentityQuery) {
    257                     this.aclsByObjectIdentity = aclsByObjectIdentityQuery;
    258                 }
    259
    260                 /***
    261                  * Allows the default query string used to retrieve ACLs based
    262                  * on object identity to be overriden, if default table or
    263                  * column names need to be changed. The default query is
    264                  * {@link #DEF_ACLS_BY_OBJECT_IDENTITY_QUERY}; when modifying
    265                  * this query, ensure that all returned columns are mapped
    266                  * back to the same column names as in the default query.
    267                  *
    268                  * @param queryString The query string to set
    269                  */
    270                 public void setAclsByObjectIdentityQuery(String queryString) {
    271                     aclsByObjectIdentityQuery = queryString;
    272                 }
    273
    274                 public void setObjectProperties(
    275                     MappingSqlQuery objectPropertiesQuery) {
    276                     this.objectProperties = objectPropertiesQuery;
    277                 }
    278
    279                 public void setObjectPropertiesQuery(String queryString) {
    280                     objectPropertiesQuery = queryString;
    281                 }
    282
    283                 //~ Inner Classes ==========================================================
    284
    285                 /***
    286                  * Used to hold details of a domain object instance's
    287                  * properties, or an individual ACL entry.
    288                  *
    289                  * <P>
    290                  * Not all properties will be set. The actual properties set
    291                  * will depend on which <code>MappingSqlQuery</code> creates
    292                  * the object.
    293                  * </p>
    294                  *
    295                  * <P>
    296                  * Does not enforce <code>null</code>s or empty
    297                  * <code>String</code>s as this is performed by the
    298                  * <code>MappingSqlQuery</code> objects (or preferably the
    299                  * backend RDBMS via schema constraints).
    300                  * </p>
    301                  */
    302                 protected final class AclDetailsHolder {
    303                     private AclObjectIdentity aclObjectIdentity;
    304                     private AclObjectIdentity aclObjectParentIdentity;
    305                     private Class aclClass;
    306                     private Object recipient;
    307                     private int mask;
    308                     private long foreignKeyId;
    309
    310                     /***
    311                      * Record details of an individual ACL entry (usually from
    312                      * the ACL_PERMISSION table)
    313                      *
    314                      * @param recipient the recipient
    315                      * @param mask the integer to be masked
    316                      */
    317                     public AclDetailsHolder(Object recipient, int mask) {
    318                         this.recipient = recipient;
    319                         this.mask = mask;
    320                     }
    321
    322                     /***
    323                      * Record details of a domain object instance's properties
    324                      * (usually from the ACL_OBJECT_IDENTITY table)
    325                      *
    326                      * @param foreignKeyId used by the
    327                      *        <code>AclsByObjectIdentityMapping</code> to
    328                      *        locate the individual ACL entries
    329                      * @param aclObjectIdentity the object identity of the
    330                      *        domain object instance
    331                      * @param aclObjectParentIdentity the object identity of
    332                      *        the domain object instance's parent
    333                      * @param aclClass the class of which a new instance which
    334                      *        should be created for each individual ACL entry
    335                      *        (or an inheritence "holder" class if there are
    336                      *        no ACL entries)
    337                      */
    338                     public AclDetailsHolder(long foreignKeyId,
    339                         AclObjectIdentity aclObjectIdentity,
    340                         AclObjectIdentity aclObjectParentIdentity,
    341                         Class aclClass) {
    342                         this.foreignKeyId = foreignKeyId;
    343                         this.aclObjectIdentity = aclObjectIdentity;
    344                         this.aclObjectParentIdentity = aclObjectParentIdentity;
    345                         this.aclClass = aclClass;
    346                     }
    347
    348                     public Class getAclClass() {
    349                         return aclClass;
    350                     }
    351
    352                     public AclObjectIdentity getAclObjectIdentity() {
    353                         return aclObjectIdentity;
    354                     }
    355
    356                     public AclObjectIdentity getAclObjectParentIdentity() {
    357                         return aclObjectParentIdentity;
    358                     }
    359
    360                     public long getForeignKeyId() {
    361                         return foreignKeyId;
    362                     }
    363
    364                     public int getMask() {
    365                         return mask;
    366                     }
    367
    368                     public Object getRecipient() {
    369                         return recipient;
    370                     }
    371                 }
    372
    373                 /***
    374                  * Query object to look up individual ACL entries.
    375                  *
    376                  * <P>
    377                  * Returns the generic <code>AclDetailsHolder</code> object.
    378                  * </p>
    379                  *
    380                  * <P>
    381                  * Guarantees to never return <code>null</code> (exceptions are
    382                  * thrown in the event of any issues).
    383                  * </p>
    384                  *
    385                  * <P>
    386                  * The executed SQL requires the following information be made
    387                  * available from the indicated placeholders: 1. RECIPIENT, 2.
    388                  * MASK.
    389                  * </p>
    390                  */
    391                 protected class AclsByObjectIdentityMapping
    392                     extends MappingSqlQuery {
    393                     protected AclsByObjectIdentityMapping(DataSource ds) {
    394                         super(ds, aclsByObjectIdentityQuery);
    395                         declareParameter(new SqlParameter(Types.BIGINT));
    396                         compile();
    397                     }
    398
    399                     protected Object mapRow(ResultSet rs, int rownum)
    400                         throws SQLException {
    401                         String recipient = rs.getString(1);
    402                         int mask = rs.getInt(2);
    403                         Assert.hasText(recipient, "recipient required");
    404
    405                         return new AclDetailsHolder(recipient, mask);
    406                     }
    407                 }
    408
    409                 /***
    410                  * Query object to look up properties for an object identity.
    411                  *
    412                  * <P>
    413                  * Returns the generic <code>AclDetailsHolder</code> object.
    414                  * </p>
    415                  *
    416                  * <P>
    417                  * Guarantees to never return <code>null</code> (exceptions are
    418                  * thrown in the event of any issues).
    419                  * </p>
    420                  *
    421                  * <P>
    422                  * The executed SQL requires the following information be made
    423                  * available from the indicated placeholders: 1. ID, 2.
    424                  * OBJECT_IDENTITY, 3. ACL_CLASS and 4.
    425                  * PARENT_OBJECT_IDENTITY.
    426                  * </p>
    427                  */
    428                 protected class ObjectPropertiesMapping extends MappingSqlQuery {
    429                     protected ObjectPropertiesMapping(DataSource ds) {
    430                         super(ds, objectPropertiesQuery);
    431                         declareParameter(new SqlParameter(Types.VARCHAR));
    432                         compile();
    433                     }
    434
    435                     private AclObjectIdentity buildIdentity(String identity) {
    436                         if (identity == null) {
    437                             // Must be an empty parent, so return null
    438                             return null;
    439                         }
    440
    441                         int delim = identity.lastIndexOf(":");
    442                         String classname = identity.substring(0, delim);
    443                         String id = identity.substring(delim + 1);
    444
    445                         return new NamedEntityObjectIdentity(classname, id);
    446                     }
    447
    448                     protected Object mapRow(ResultSet rs, int rownum)
    449                         throws SQLException {
    450                         long id = rs.getLong(1); // required
    451                         String objectIdentity = rs.getString(2); // required
    452                         String aclClass = rs.getString(3); // required
    453                         String parentObjectIdentity = rs.getString(4); // optional
    454                         Assert.hasText(objectIdentity,
    455                             "required DEF_OBJECT_PROPERTIES_QUERY value (objectIdentity) returned null or empty");
    456                         Assert.hasText(aclClass,
    457                             "required DEF_OBJECT_PROPERTIES_QUERY value (aclClass) returned null or empty");
    458
    459                         Class aclClazz;
    460
    461                         try {
    462                             aclClazz = this.getClass().getClassLoader()
    463                                            .loadClass(aclClass);
    464                         } catch (ClassNotFoundException cnf) {
    465                             throw new IllegalArgumentException(cnf.getMessage());
    466                         }
    467
    468                         return new AclDetailsHolder(id,
    469                             buildIdentity(objectIdentity),
    470                             buildIdentity(parentObjectIdentity), aclClazz);
    471                     }
    472                 }
    473             }