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: package org.acegisecurity.acls.jdbc;
016:
017: import org.acegisecurity.Authentication;
018:
019: import org.acegisecurity.acls.AccessControlEntry;
020: import org.acegisecurity.acls.Acl;
021: import org.acegisecurity.acls.AlreadyExistsException;
022: import org.acegisecurity.acls.ChildrenExistException;
023: import org.acegisecurity.acls.MutableAcl;
024: import org.acegisecurity.acls.MutableAclService;
025: import org.acegisecurity.acls.NotFoundException;
026: import org.acegisecurity.acls.domain.AccessControlEntryImpl;
027: import org.acegisecurity.acls.objectidentity.ObjectIdentity;
028: import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
029: import org.acegisecurity.acls.sid.GrantedAuthoritySid;
030: import org.acegisecurity.acls.sid.PrincipalSid;
031: import org.acegisecurity.acls.sid.Sid;
032:
033: import org.acegisecurity.context.SecurityContextHolder;
034:
035: import org.springframework.dao.DataAccessException;
036:
037: import org.springframework.jdbc.core.BatchPreparedStatementSetter;
038:
039: import org.springframework.transaction.support.TransactionSynchronizationManager;
040:
041: import org.springframework.util.Assert;
042:
043: import java.lang.reflect.Array;
044:
045: import java.sql.PreparedStatement;
046: import java.sql.SQLException;
047:
048: import java.util.List;
049:
050: import javax.sql.DataSource;
051:
052: /**
053: * Provides a base implementation of {@link MutableAclService}.
054: *
055: * @author Ben Alex
056: * @author Johannes Zlattinger
057: * @version $Id: JdbcMutableAclService.java 1784 2007-02-24 21:00:24Z luke_t $
058: */
059: public class JdbcMutableAclService extends JdbcAclService implements
060: MutableAclService {
061: //~ Instance fields ================================================================================================
062:
063: private AclCache aclCache;
064: private String deleteClassByClassNameString = "DELETE FROM acl_class WHERE class=?";
065: private String deleteEntryByObjectIdentityForeignKey = "DELETE FROM acl_entry WHERE acl_object_identity=?";
066: private String deleteObjectIdentityByPrimaryKey = "DELETE FROM acl_object_identity WHERE id=?";
067: private String identityQuery = "call identity()";
068: private String insertClass = "INSERT INTO acl_class (id, class) VALUES (null, ?)";
069: private String insertEntry = "INSERT INTO acl_entry "
070: + "(id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure)"
071: + "VALUES (null, ?, ?, ?, ?, ?, ?, ?)";
072: private String insertObjectIdentity = "INSERT INTO acl_object_identity "
073: + "(id, object_id_class, object_id_identity, owner_sid, entries_inheriting) "
074: + "VALUES (null, ?, ?, ?, ?)";
075: private String insertSid = "INSERT INTO acl_sid (id, principal, sid) VALUES (null, ?, ?)";
076: private String selectClassPrimaryKey = "SELECT id FROM acl_class WHERE class=?";
077: private String selectCountObjectIdentityRowsForParticularClassNameString = "SELECT COUNT(acl_object_identity.id) "
078: + "FROM acl_object_identity, acl_class WHERE acl_class.id = acl_object_identity.object_id_class and class=?";
079: private String selectObjectIdentityPrimaryKey = "SELECT acl_object_identity.id FROM acl_object_identity, acl_class "
080: + "WHERE acl_object_identity.object_id_class = acl_class.id and acl_class.class=? "
081: + "and acl_object_identity.object_id_identity = ?";
082: private String selectSidPrimaryKey = "SELECT id FROM acl_sid WHERE principal=? AND sid=?";
083: private String updateObjectIdentity = "UPDATE acl_object_identity SET "
084: + "parent_object = ?, owner_sid = ?, entries_inheriting = ?"
085: + "where id = ?";
086:
087: //~ Constructors ===================================================================================================
088:
089: public JdbcMutableAclService(DataSource dataSource,
090: LookupStrategy lookupStrategy, AclCache aclCache) {
091: super (dataSource, lookupStrategy);
092: Assert.notNull(aclCache, "AclCache required");
093: this .aclCache = aclCache;
094: }
095:
096: //~ Methods ========================================================================================================
097:
098: public MutableAcl createAcl(ObjectIdentity objectIdentity)
099: throws AlreadyExistsException {
100: Assert.notNull(objectIdentity, "Object Identity required");
101:
102: // Check this object identity hasn't already been persisted
103: if (retrieveObjectIdentityPrimaryKey(objectIdentity) != null) {
104: throw new AlreadyExistsException("Object identity '"
105: + objectIdentity + "' already exists");
106: }
107:
108: // Need to retrieve the current principal, in order to know who "owns" this ACL (can be changed later on)
109: Authentication auth = SecurityContextHolder.getContext()
110: .getAuthentication();
111: PrincipalSid sid = new PrincipalSid(auth);
112:
113: // Create the acl_object_identity row
114: createObjectIdentity(objectIdentity, sid);
115:
116: // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
117: Acl acl = readAclById(objectIdentity);
118: Assert.isInstanceOf(MutableAcl.class, acl,
119: "MutableAcl should be been returned");
120:
121: return (MutableAcl) acl;
122: }
123:
124: /**
125: * Creates a new row in acl_entry for every ACE defined in the passed MutableAcl object.
126: *
127: * @param acl containing the ACEs to insert
128: */
129: protected void createEntries(final MutableAcl acl) {
130: jdbcTemplate.batchUpdate(insertEntry,
131: new BatchPreparedStatementSetter() {
132: public int getBatchSize() {
133: return acl.getEntries().length;
134: }
135:
136: public void setValues(PreparedStatement stmt, int i)
137: throws SQLException {
138: AccessControlEntry entry_ = (AccessControlEntry) Array
139: .get(acl.getEntries(), i);
140: Assert
141: .isTrue(
142: entry_ instanceof AccessControlEntryImpl,
143: "Unknown ACE class");
144:
145: AccessControlEntryImpl entry = (AccessControlEntryImpl) entry_;
146:
147: stmt.setLong(1, ((Long) acl.getId())
148: .longValue());
149: stmt.setInt(2, i);
150: stmt.setLong(3, createOrRetrieveSidPrimaryKey(
151: entry.getSid(), true).longValue());
152: stmt.setInt(4, entry.getPermission().getMask());
153: stmt.setBoolean(5, entry.isGranting());
154: stmt.setBoolean(6, entry.isAuditSuccess());
155: stmt.setBoolean(7, entry.isAuditFailure());
156: }
157: });
158: }
159:
160: /**
161: * Creates an entry in the acl_object_identity table for the passed ObjectIdentity. The Sid is also
162: * necessary, as acl_object_identity has defined the sid column as non-null.
163: *
164: * @param object to represent an acl_object_identity for
165: * @param owner for the SID column (will be created if there is no acl_sid entry for this particular Sid already)
166: */
167: protected void createObjectIdentity(ObjectIdentity object, Sid owner) {
168: Long sidId = createOrRetrieveSidPrimaryKey(owner, true);
169: Long classId = createOrRetrieveClassPrimaryKey(object
170: .getJavaType(), true);
171: jdbcTemplate.update(insertObjectIdentity, new Object[] {
172: classId, object.getIdentifier().toString(), sidId,
173: new Boolean(true) });
174: }
175:
176: /**
177: * Retrieves the primary key from acl_class, creating a new row if needed and the allowCreate property is
178: * true.
179: *
180: * @param clazz to find or create an entry for (this implementation uses the fully-qualified class name String)
181: * @param allowCreate true if creation is permitted if not found
182: *
183: * @return the primary key or null if not found
184: */
185: protected Long createOrRetrieveClassPrimaryKey(Class clazz,
186: boolean allowCreate) {
187: List classIds = jdbcTemplate.queryForList(
188: selectClassPrimaryKey,
189: new Object[] { clazz.getName() }, Long.class);
190: Long classId = null;
191:
192: if (classIds.isEmpty()) {
193: if (allowCreate) {
194: classId = null;
195: jdbcTemplate.update(insertClass, new Object[] { clazz
196: .getName() });
197: Assert.isTrue(TransactionSynchronizationManager
198: .isSynchronizationActive(),
199: "Transaction must be running");
200: classId = new Long(jdbcTemplate
201: .queryForLong(identityQuery));
202: }
203: } else {
204: classId = (Long) classIds.iterator().next();
205: }
206:
207: return classId;
208: }
209:
210: /**
211: * Retrieves the primary key from acl_sid, creating a new row if needed and the allowCreate property is
212: * true.
213: *
214: * @param sid to find or create
215: * @param allowCreate true if creation is permitted if not found
216: *
217: * @return the primary key or null if not found
218: *
219: * @throws IllegalArgumentException DOCUMENT ME!
220: */
221: protected Long createOrRetrieveSidPrimaryKey(Sid sid,
222: boolean allowCreate) {
223: Assert.notNull(sid, "Sid required");
224:
225: String sidName = null;
226: boolean principal = true;
227:
228: if (sid instanceof PrincipalSid) {
229: sidName = ((PrincipalSid) sid).getPrincipal();
230: } else if (sid instanceof GrantedAuthoritySid) {
231: sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority();
232: principal = false;
233: } else {
234: throw new IllegalArgumentException(
235: "Unsupported implementation of Sid");
236: }
237:
238: List sidIds = jdbcTemplate.queryForList(selectSidPrimaryKey,
239: new Object[] { new Boolean(principal), sidName },
240: Long.class);
241: Long sidId = null;
242:
243: if (sidIds.isEmpty()) {
244: if (allowCreate) {
245: sidId = null;
246: jdbcTemplate.update(insertSid, new Object[] {
247: new Boolean(principal), sidName });
248: Assert.isTrue(TransactionSynchronizationManager
249: .isSynchronizationActive(),
250: "Transaction must be running");
251: sidId = new Long(jdbcTemplate
252: .queryForLong(identityQuery));
253: }
254: } else {
255: sidId = (Long) sidIds.iterator().next();
256: }
257:
258: return sidId;
259: }
260:
261: public void deleteAcl(ObjectIdentity objectIdentity,
262: boolean deleteChildren) throws ChildrenExistException {
263: Assert.notNull(objectIdentity, "Object Identity required");
264: Assert.notNull(objectIdentity.getIdentifier(),
265: "Object Identity doesn't provide an identifier");
266:
267: // Recursively call this method for children, or handle children if they don't want automatic recursion
268: ObjectIdentity[] children = findChildren(objectIdentity);
269:
270: if (deleteChildren) {
271: for (int i = 0; i < children.length; i++) {
272: deleteAcl(children[i], true);
273: }
274: } else if (children.length > 0) {
275: throw new ChildrenExistException("Cannot delete '"
276: + objectIdentity + "' (has " + children.length
277: + " children)");
278: }
279:
280: // Delete this ACL's ACEs in the acl_entry table
281: deleteEntries(objectIdentity);
282:
283: // Delete this ACL's acl_object_identity row
284: deleteObjectIdentityAndOptionallyClass(objectIdentity);
285:
286: // Clear the cache
287: aclCache.evictFromCache(objectIdentity);
288: }
289:
290: /**
291: * Deletes all ACEs defined in the acl_entry table belonging to the presented ObjectIdentity
292: *
293: * @param oid the rows in acl_entry to delete
294: */
295: protected void deleteEntries(ObjectIdentity oid) {
296: jdbcTemplate.update(deleteEntryByObjectIdentityForeignKey,
297: new Object[] { retrieveObjectIdentityPrimaryKey(oid) });
298: }
299:
300: /**
301: * Deletes a single row from acl_object_identity that is associated with the presented ObjectIdentity. In
302: * addition, deletes the corresponding row from acl_class if there are no more entries in acl_object_identity that
303: * use that particular acl_class. This keeps the acl_class table reasonably small.
304: *
305: * @param oid to delete the acl_object_identity (and clean up acl_class for that class name if appropriate)
306: */
307: protected void deleteObjectIdentityAndOptionallyClass(
308: ObjectIdentity oid) {
309: // Delete the acl_object_identity row
310: jdbcTemplate.update(deleteObjectIdentityByPrimaryKey,
311: new Object[] { retrieveObjectIdentityPrimaryKey(oid) });
312:
313: // Delete the acl_class row, assuming there are no other references to it in acl_object_identity
314: Object[] className = { oid.getJavaType().getName() };
315: long numObjectIdentities = jdbcTemplate
316: .queryForLong(
317: selectCountObjectIdentityRowsForParticularClassNameString,
318: className);
319:
320: if (numObjectIdentities == 0) {
321: // No more rows
322: jdbcTemplate
323: .update(deleteClassByClassNameString, className);
324: }
325: }
326:
327: /**
328: * Retrieves the primary key from the acl_object_identity table for the passed ObjectIdentity. Unlike some
329: * other methods in this implementation, this method will NOT create a row (use {@link
330: * #createObjectIdentity(ObjectIdentity, Sid)} instead).
331: *
332: * @param oid to find
333: *
334: * @return the object identity or null if not found
335: */
336: protected Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) {
337: try {
338: return new Long(jdbcTemplate.queryForLong(
339: selectObjectIdentityPrimaryKey, new Object[] {
340: oid.getJavaType().getName(),
341: oid.getIdentifier() }));
342: } catch (DataAccessException notFound) {
343: return null;
344: }
345: }
346:
347: /**
348: * This implementation will simply delete all ACEs in the database and recreate them on each invocation of
349: * this method. A more comprehensive implementation might use dirty state checking, or more likely use ORM
350: * capabilities for create, update and delete operations of {@link MutableAcl}.
351: *
352: * @param acl DOCUMENT ME!
353: *
354: * @return DOCUMENT ME!
355: *
356: * @throws NotFoundException DOCUMENT ME!
357: */
358: public MutableAcl updateAcl(MutableAcl acl)
359: throws NotFoundException {
360: Assert.notNull(acl.getId(),
361: "Object Identity doesn't provide an identifier");
362:
363: // Delete this ACL's ACEs in the acl_entry table
364: deleteEntries(acl.getObjectIdentity());
365:
366: // Create this ACL's ACEs in the acl_entry table
367: createEntries(acl);
368:
369: // Change the mutable columns in acl_object_identity
370: updateObjectIdentity(acl);
371:
372: // Clear the cache
373: aclCache.evictFromCache(acl.getObjectIdentity());
374:
375: // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
376: return (MutableAcl) super .readAclById(acl.getObjectIdentity());
377: }
378:
379: /**
380: * Updates an existing acl_object_identity row, with new information presented in the passed MutableAcl
381: * object. Also will create an acl_sid entry if needed for the Sid that owns the MutableAcl.
382: *
383: * @param acl to modify (a row must already exist in acl_object_identity)
384: *
385: * @throws NotFoundException DOCUMENT ME!
386: */
387: protected void updateObjectIdentity(MutableAcl acl) {
388: Long parentId = null;
389:
390: if (acl.getParentAcl() != null) {
391: Assert.isInstanceOf(ObjectIdentityImpl.class, acl
392: .getParentAcl().getObjectIdentity(),
393: "Implementation only supports ObjectIdentityImpl");
394:
395: ObjectIdentityImpl oii = (ObjectIdentityImpl) acl
396: .getParentAcl().getObjectIdentity();
397: parentId = retrieveObjectIdentityPrimaryKey(oii);
398: }
399:
400: Assert.notNull(acl.getOwner(),
401: "Owner is required in this implementation");
402:
403: Long ownerSid = createOrRetrieveSidPrimaryKey(acl.getOwner(),
404: true);
405: int count = jdbcTemplate.update(updateObjectIdentity,
406: new Object[] { parentId, ownerSid,
407: new Boolean(acl.isEntriesInheriting()),
408: acl.getId() });
409:
410: if (count != 1) {
411: throw new NotFoundException(
412: "Unable to locate ACL to update");
413: }
414: }
415: }
|