1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
66
67 protected MappingSqlQuery aclsByObjectIdentity;
68 protected MappingSqlQuery objectProperties;
69 private String aclsByObjectIdentityQuery;
70 private String objectPropertiesQuery;
71
72
73
74 public JdbcDaoImpl() {
75 aclsByObjectIdentityQuery = DEF_ACLS_BY_OBJECT_IDENTITY_QUERY;
76 objectPropertiesQuery = DEF_OBJECT_PROPERTIES_QUERY;
77 }
78
79
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
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
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
145 entry.setMask(0);
146 entry.setRecipient(RECIPIENT_USED_FOR_INHERITENCE_MARKER);
147 } else {
148
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;
191 }
192
193
194 List objects = objectProperties.execute(aclObjectIdentityString);
195
196 if (objects.size() == 0) {
197
198 return null;
199 }
200
201
202 AclDetailsHolder propertiesInformation = (AclDetailsHolder) objects
203 .get(0);
204
205
206 List acls = aclsByObjectIdentity.execute(propertiesInformation
207 .getForeignKeyId());
208
209 if (acls.size() == 0) {
210
211 return new BasicAclEntry[] {createBasicAclEntry(propertiesInformation,
212 null)};
213 } else {
214
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
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
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);
451 String objectIdentity = rs.getString(2);
452 String aclClass = rs.getString(3);
453 String parentObjectIdentity = rs.getString(4);
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 }