001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.acl.basic.jdbc;
017:
018: import org.acegisecurity.acl.basic.AclObjectIdentity;
019: import org.acegisecurity.acl.basic.BasicAclDao;
020: import org.acegisecurity.acl.basic.BasicAclEntry;
021: import org.acegisecurity.acl.basic.NamedEntityObjectIdentity;
022:
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025:
026: import org.springframework.context.ApplicationContextException;
027:
028: import org.springframework.jdbc.core.SqlParameter;
029: import org.springframework.jdbc.core.support.JdbcDaoSupport;
030: import org.springframework.jdbc.object.MappingSqlQuery;
031:
032: import org.springframework.util.Assert;
033:
034: import java.sql.ResultSet;
035: import java.sql.SQLException;
036: import java.sql.Types;
037:
038: import java.util.List;
039: import java.util.Vector;
040:
041: import javax.sql.DataSource;
042:
043: /**
044: * Retrieves ACL details from a JDBC location.
045: * <p>
046: * A default database structure is assumed. This may be overridden by setting the default query strings to use.
047: * If this does not provide enough flexibility, another strategy would be to subclass this class and override the
048: * {@link MappingSqlQuery} instance used, via the {@link #initMappingSqlQueries()} extension point.
049: * </p>
050: */
051: public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
052: //~ Static fields/initializers =====================================================================================
053:
054: public static final String RECIPIENT_USED_FOR_INHERITENCE_MARKER = "___INHERITENCE_MARKER_ONLY___";
055: public static final String DEF_ACLS_BY_OBJECT_IDENTITY_QUERY = "SELECT RECIPIENT, MASK FROM acl_permission WHERE acl_object_identity = ?";
056: public static final String DEF_OBJECT_PROPERTIES_QUERY = "SELECT CHILD.ID, "
057: + "CHILD.OBJECT_IDENTITY, "
058: + "CHILD.ACL_CLASS, "
059: + "PARENT.OBJECT_IDENTITY as PARENT_OBJECT_IDENTITY "
060: + "FROM acl_object_identity as CHILD "
061: + "LEFT OUTER JOIN acl_object_identity as PARENT ON CHILD.parent_object=PARENT.id "
062: + "WHERE CHILD.object_identity = ?";
063: private static final Log logger = LogFactory
064: .getLog(JdbcDaoImpl.class);
065:
066: //~ Instance fields ================================================================================================
067:
068: protected MappingSqlQuery aclsByObjectIdentity;
069: protected MappingSqlQuery objectProperties;
070: private String aclsByObjectIdentityQuery;
071: private String objectPropertiesQuery;
072:
073: //~ Constructors ===================================================================================================
074:
075: public JdbcDaoImpl() {
076: aclsByObjectIdentityQuery = DEF_ACLS_BY_OBJECT_IDENTITY_QUERY;
077: objectPropertiesQuery = DEF_OBJECT_PROPERTIES_QUERY;
078: }
079:
080: //~ Methods ========================================================================================================
081:
082: /**
083: * Responsible for covering a <code>AclObjectIdentity</code> to a <code>String</code> that can be located
084: * in the RDBMS.
085: *
086: * @param aclObjectIdentity to locate
087: *
088: * @return the object identity as a <code>String</code>
089: */
090: protected String convertAclObjectIdentityToString(
091: AclObjectIdentity aclObjectIdentity) {
092: // Ensure we can process this type of AclObjectIdentity
093: Assert
094: .isInstanceOf(
095: NamedEntityObjectIdentity.class,
096: aclObjectIdentity,
097: "Only aclObjectIdentity of type NamedEntityObjectIdentity supported (was passed: "
098: + aclObjectIdentity + ")");
099:
100: NamedEntityObjectIdentity neoi = (NamedEntityObjectIdentity) aclObjectIdentity;
101:
102: // Compose the String we expect to find in the RDBMS
103: return neoi.getClassname() + ":" + neoi.getId();
104: }
105:
106: /**
107: * Constructs an individual <code>BasicAclEntry</code> from the passed <code>AclDetailsHolder</code>s.<P>Guarantees
108: * to never return <code>null</code> (exceptions are thrown in the event of any issues).</p>
109: *
110: * @param propertiesInformation mandatory information about which instance to create, the object identity, and the
111: * parent object identity (<code>null</code> or empty <code>String</code>s prohibited for
112: * <code>aclClass</code> and <code>aclObjectIdentity</code>
113: * @param aclInformation optional information about the individual ACL record (if <code>null</code> only an
114: * "inheritence marker" instance is returned which will include a recipient of {@link
115: * #RECIPIENT_USED_FOR_INHERITENCE_MARKER} ; if not <code>null</code>, it is prohibited to present
116: * <code>null</code> or an empty <code>String</code> for <code>recipient</code>)
117: *
118: * @return a fully populated instance suitable for use by external objects
119: *
120: * @throws IllegalArgumentException if the indicated ACL class could not be created
121: */
122: private BasicAclEntry createBasicAclEntry(
123: AclDetailsHolder propertiesInformation,
124: AclDetailsHolder aclInformation) {
125: BasicAclEntry entry;
126:
127: try {
128: entry = (BasicAclEntry) propertiesInformation.getAclClass()
129: .newInstance();
130: } catch (InstantiationException ie) {
131: throw new IllegalArgumentException(ie.getMessage());
132: } catch (IllegalAccessException iae) {
133: throw new IllegalArgumentException(iae.getMessage());
134: }
135:
136: entry.setAclObjectIdentity(propertiesInformation
137: .getAclObjectIdentity());
138: entry.setAclObjectParentIdentity(propertiesInformation
139: .getAclObjectParentIdentity());
140:
141: if (aclInformation == null) {
142: // this is an inheritence marker instance only
143: entry.setMask(0);
144: entry.setRecipient(RECIPIENT_USED_FOR_INHERITENCE_MARKER);
145: } else {
146: // this is an individual ACL entry
147: entry.setMask(aclInformation.getMask());
148: entry.setRecipient(aclInformation.getRecipient());
149: }
150:
151: return entry;
152: }
153:
154: /**
155: * Returns the ACLs associated with the requested <code>AclObjectIdentity</code>.<P>The {@link
156: * BasicAclEntry}s returned by this method will have <code>String</code>-based recipients. This will not be a
157: * problem if you are using the <code>GrantedAuthorityEffectiveAclsResolver</code>, which is the default
158: * configured against <code>BasicAclProvider</code>.</p>
159: * <P>This method will only return ACLs for requests where the <code>AclObjectIdentity</code> is of type
160: * {@link NamedEntityObjectIdentity}. Of course, you can subclass or replace this class and support your own
161: * custom <code>AclObjectIdentity</code> types.</p>
162: *
163: * @param aclObjectIdentity for which ACL information is required (cannot be <code>null</code> and must be an
164: * instance of <code>NamedEntityObjectIdentity</code>)
165: *
166: * @return the ACLs that apply (without any <code>null</code>s inside the array), or <code>null</code> if not found
167: * or if an incompatible <code>AclObjectIdentity</code> was requested
168: */
169: public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity) {
170: String aclObjectIdentityString;
171:
172: try {
173: aclObjectIdentityString = convertAclObjectIdentityToString(aclObjectIdentity);
174: } catch (IllegalArgumentException unsupported) {
175: return null; // pursuant to contract described in JavaDocs above
176: }
177:
178: // Lookup the object's main properties from the RDBMS (guaranteed no nulls)
179: List objects = objectProperties
180: .execute(aclObjectIdentityString);
181:
182: if (objects.size() == 0) {
183: // this is an unknown object identity string
184: return null;
185: }
186:
187: // Cast to an object properties holder (there should only be one record)
188: AclDetailsHolder propertiesInformation = (AclDetailsHolder) objects
189: .get(0);
190:
191: // Lookup the object's ACLs from RDBMS (guaranteed no nulls)
192: List acls = aclsByObjectIdentity.execute(propertiesInformation
193: .getForeignKeyId());
194:
195: if (acls.size() == 0) {
196: // return merely an inheritence marker (as we know about the object but it has no related ACLs)
197: return new BasicAclEntry[] { createBasicAclEntry(
198: propertiesInformation, null) };
199: } else {
200: // return the individual ACL instances
201: AclDetailsHolder[] aclHolders = (AclDetailsHolder[]) acls
202: .toArray(new AclDetailsHolder[] {});
203: List toReturnAcls = new Vector();
204:
205: for (int i = 0; i < aclHolders.length; i++) {
206: toReturnAcls.add(createBasicAclEntry(
207: propertiesInformation, aclHolders[i]));
208: }
209:
210: return (BasicAclEntry[]) toReturnAcls
211: .toArray(new BasicAclEntry[] {});
212: }
213: }
214:
215: public MappingSqlQuery getAclsByObjectIdentity() {
216: return aclsByObjectIdentity;
217: }
218:
219: public String getAclsByObjectIdentityQuery() {
220: return aclsByObjectIdentityQuery;
221: }
222:
223: public String getObjectPropertiesQuery() {
224: return objectPropertiesQuery;
225: }
226:
227: protected void initDao() throws ApplicationContextException {
228: initMappingSqlQueries();
229: }
230:
231: /**
232: * Extension point to allow other MappingSqlQuery objects to be substituted in a subclass
233: */
234: protected void initMappingSqlQueries() {
235: setAclsByObjectIdentity(new AclsByObjectIdentityMapping(
236: getDataSource()));
237: setObjectProperties(new ObjectPropertiesMapping(getDataSource()));
238: }
239:
240: public void setAclsByObjectIdentity(
241: MappingSqlQuery aclsByObjectIdentityQuery) {
242: this .aclsByObjectIdentity = aclsByObjectIdentityQuery;
243: }
244:
245: /**
246: * Allows the default query string used to retrieve ACLs based on object identity to be overriden, if
247: * default table or column names need to be changed. The default query is {@link
248: * #DEF_ACLS_BY_OBJECT_IDENTITY_QUERY}; when modifying this query, ensure that all returned columns are mapped
249: * back to the same column names as in the default query.
250: *
251: * @param queryString The query string to set
252: */
253: public void setAclsByObjectIdentityQuery(String queryString) {
254: aclsByObjectIdentityQuery = queryString;
255: }
256:
257: public void setObjectProperties(
258: MappingSqlQuery objectPropertiesQuery) {
259: this .objectProperties = objectPropertiesQuery;
260: }
261:
262: public void setObjectPropertiesQuery(String queryString) {
263: objectPropertiesQuery = queryString;
264: }
265:
266: //~ Inner Classes ==================================================================================================
267:
268: /**
269: * Used to hold details of a domain object instance's properties, or an individual ACL entry.<P>Not all
270: * properties will be set. The actual properties set will depend on which <code>MappingSqlQuery</code> creates the
271: * object.</p>
272: * <P>Does not enforce <code>null</code>s or empty <code>String</code>s as this is performed by the
273: * <code>MappingSqlQuery</code> objects (or preferably the backend RDBMS via schema constraints).</p>
274: */
275: protected final class AclDetailsHolder {
276: private AclObjectIdentity aclObjectIdentity;
277: private AclObjectIdentity aclObjectParentIdentity;
278: private Class aclClass;
279: private Object recipient;
280: private int mask;
281: private long foreignKeyId;
282:
283: /**
284: * Record details of an individual ACL entry (usually from the
285: * ACL_PERMISSION table)
286: *
287: * @param recipient the recipient
288: * @param mask the integer to be masked
289: */
290: public AclDetailsHolder(Object recipient, int mask) {
291: this .recipient = recipient;
292: this .mask = mask;
293: }
294:
295: /**
296: * Record details of a domain object instance's properties (usually
297: * from the ACL_OBJECT_IDENTITY table)
298: *
299: * @param foreignKeyId used by the
300: * <code>AclsByObjectIdentityMapping</code> to locate the
301: * individual ACL entries
302: * @param aclObjectIdentity the object identity of the domain object
303: * instance
304: * @param aclObjectParentIdentity the object identity of the domain
305: * object instance's parent
306: * @param aclClass the class of which a new instance which should be
307: * created for each individual ACL entry (or an inheritence
308: * "holder" class if there are no ACL entries)
309: */
310: public AclDetailsHolder(long foreignKeyId,
311: AclObjectIdentity aclObjectIdentity,
312: AclObjectIdentity aclObjectParentIdentity,
313: Class aclClass) {
314: this .foreignKeyId = foreignKeyId;
315: this .aclObjectIdentity = aclObjectIdentity;
316: this .aclObjectParentIdentity = aclObjectParentIdentity;
317: this .aclClass = aclClass;
318: }
319:
320: public Class getAclClass() {
321: return aclClass;
322: }
323:
324: public AclObjectIdentity getAclObjectIdentity() {
325: return aclObjectIdentity;
326: }
327:
328: public AclObjectIdentity getAclObjectParentIdentity() {
329: return aclObjectParentIdentity;
330: }
331:
332: public long getForeignKeyId() {
333: return foreignKeyId;
334: }
335:
336: public int getMask() {
337: return mask;
338: }
339:
340: public Object getRecipient() {
341: return recipient;
342: }
343: }
344:
345: /**
346: * Query object to look up individual ACL entries.<P>Returns the generic <code>AclDetailsHolder</code>
347: * object.</p>
348: * <P>Guarantees to never return <code>null</code> (exceptions are thrown in the event of any issues).</p>
349: * <P>The executed SQL requires the following information be made available from the indicated
350: * placeholders: 1. RECIPIENT, 2. MASK.</p>
351: */
352: protected class AclsByObjectIdentityMapping extends MappingSqlQuery {
353: protected AclsByObjectIdentityMapping(DataSource ds) {
354: super (ds, aclsByObjectIdentityQuery);
355: declareParameter(new SqlParameter(Types.BIGINT));
356: compile();
357: }
358:
359: protected Object mapRow(ResultSet rs, int rownum)
360: throws SQLException {
361: String recipient = rs.getString(1);
362: int mask = rs.getInt(2);
363: Assert.hasText(recipient, "recipient required");
364:
365: return new AclDetailsHolder(recipient, mask);
366: }
367: }
368:
369: /**
370: * Query object to look up properties for an object identity.<P>Returns the generic
371: * <code>AclDetailsHolder</code> object.</p>
372: * <P>Guarantees to never return <code>null</code> (exceptions are thrown in the event of any issues).</p>
373: * <P>The executed SQL requires the following information be made available from the indicated
374: * placeholders: 1. ID, 2. OBJECT_IDENTITY, 3. ACL_CLASS and 4. PARENT_OBJECT_IDENTITY.</p>
375: */
376: protected class ObjectPropertiesMapping extends MappingSqlQuery {
377: protected ObjectPropertiesMapping(DataSource ds) {
378: super (ds, objectPropertiesQuery);
379: declareParameter(new SqlParameter(Types.VARCHAR));
380: compile();
381: }
382:
383: private AclObjectIdentity buildIdentity(String identity) {
384: if (identity == null) {
385: // Must be an empty parent, so return null
386: return null;
387: }
388:
389: int delim = identity.lastIndexOf(":");
390: String classname = identity.substring(0, delim);
391: String id = identity.substring(delim + 1);
392:
393: return new NamedEntityObjectIdentity(classname, id);
394: }
395:
396: protected Object mapRow(ResultSet rs, int rownum)
397: throws SQLException {
398: long id = rs.getLong(1); // required
399: String objectIdentity = rs.getString(2); // required
400: String aclClass = rs.getString(3); // required
401: String parentObjectIdentity = rs.getString(4); // optional
402: Assert
403: .hasText(
404: objectIdentity,
405: "required DEF_OBJECT_PROPERTIES_QUERY value (objectIdentity) returned null or empty");
406: Assert
407: .hasText(aclClass,
408: "required DEF_OBJECT_PROPERTIES_QUERY value (aclClass) returned null or empty");
409:
410: Class aclClazz;
411:
412: try {
413: aclClazz = this .getClass().getClassLoader().loadClass(
414: aclClass);
415: } catch (ClassNotFoundException cnf) {
416: throw new IllegalArgumentException(cnf.getMessage());
417: }
418:
419: return new AclDetailsHolder(id,
420: buildIdentity(objectIdentity),
421: buildIdentity(parentObjectIdentity), aclClazz);
422: }
423: }
424: }
|