001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/samples/tags/sakai_2-4-1/sample-impl-access/impl/src/java/org/sakaiproject/sample/impl/SampleImplAccess.java $
003: * $Id: SampleImplAccess.java 22858 2007-03-18 18:07:19Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2007 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.sample.impl;
021:
022: import java.util.Collection;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.Set;
026: import java.util.Stack;
027:
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.sakaiproject.authz.api.SecurityAdvisor;
034: import org.sakaiproject.authz.api.SecurityService;
035: import org.sakaiproject.component.api.ServerConfigurationService;
036: import org.sakaiproject.content.api.ContentCollectionEdit;
037: import org.sakaiproject.content.api.ContentHostingService;
038: import org.sakaiproject.content.api.ContentResourceEdit;
039: import org.sakaiproject.entity.api.Entity;
040: import org.sakaiproject.entity.api.EntityAccessOverloadException;
041: import org.sakaiproject.entity.api.EntityCopyrightException;
042: import org.sakaiproject.entity.api.EntityManager;
043: import org.sakaiproject.entity.api.EntityNotDefinedException;
044: import org.sakaiproject.entity.api.EntityPermissionException;
045: import org.sakaiproject.entity.api.EntityProducer;
046: import org.sakaiproject.entity.api.HttpAccess;
047: import org.sakaiproject.entity.api.Reference;
048: import org.sakaiproject.entity.api.ResourceProperties;
049: import org.sakaiproject.entity.api.ResourcePropertiesEdit;
050: import org.sakaiproject.exception.IdInvalidException;
051: import org.sakaiproject.exception.IdUnusedException;
052: import org.sakaiproject.exception.IdUsedException;
053: import org.sakaiproject.exception.InconsistentException;
054: import org.sakaiproject.exception.OverQuotaException;
055: import org.sakaiproject.exception.PermissionException;
056: import org.sakaiproject.exception.ServerOverloadException;
057: import org.sakaiproject.exception.TypeException;
058: import org.sakaiproject.tool.api.Session;
059: import org.sakaiproject.tool.api.SessionManager;
060: import org.sakaiproject.util.StringUtil;
061: import org.w3c.dom.Document;
062: import org.w3c.dom.Element;
063:
064: /**
065: * <p>
066: * SampleImplAccess is a service implementation that demonstrates how to store application-private content in ContentHosting, and to make these
067: * available from the Access servlet under the application's control. This code would usually be found in a service implementation of the
068: * application's service API. In this example, there is no API, but there is still an implementation. For a real application, we would add the API
069: * implementation code to this code.
070: * <p>
071: * The content is stored in ContentHosting's "private" space (/private/...), in a folder named for this application. The name choosen for example this
072: * is "sampleAccess". In our init() method, we make sure that this space exists. Note the use of a SecurityAdvisor there to assure that the
073: * ContentHostingService calls pass security.
074: * <p>
075: * A SecurityAdvisor is code you provide, related only to processing of the current request (i.e. code running on the current thread). When there is a
076: * security advisor, any normal security calls are first sent to the advisor code. That code can allow the access, deny the access, or say nothing
077: * about the access, passing the decision on to any other SecurityAdvisors in a stack, or to the normal security processing code.
078: * <p>
079: * You use a security advisor when you can establish that some operation should be allowed to happen, but you are going to collaborate with some other
080: * service to make the action happen, and the other service might not know that it is ok to perform the calls you are about to make. The natural
081: * security it would invoke might not be proper for your application. So you place a security advisor on the stack and take over security for the
082: * duration of your request processing.
083: * <p>
084: * In a normal application, the end-user's HTTP interaction would result in content to save in the application's ContentHosting area, either from form
085: * fields or from a file upload. Once the body bytes are created, the application would save these in some named and located file in ContentHosting.
086: * We emulate this and show how to do it in the init() method, creating our one hosted item.
087: * <p>
088: * In a normal application, the HTML sent to the end-user would include URL references to the files in our private ContentHosting space. These might
089: * be IMG tags (to embed images), or FRAME or IFRAME addresses, or anything else HTML allows. We ask the ContentResources for their URLs so we can
090: * form our HTML responses.
091: * <p>
092: * When the request comes back from the browser to the URL of our ContentHosting items, the Access servlet fields the request. We register as a
093: * EntityProducer in our init() method so that Access will allow us to handle the heart of these requests.
094: * <p>
095: * Note that we also declare our implementation class as implementing EntityProducer, which is needed to register. This is best done in the
096: * implementation class instead of the API, since nobody using your API services really need to know you are an EntityProducer.
097: * <p>
098: * The way that we know that the URL is for us to handle is because we set the ContentResource to have a URL prefix, our own special one, that we
099: * recognize. While an application can use any form of URL after this it wants, it is convienent to just prefix the ContentHosting's natural Access
100: * URL with our special code, leaving the rest as a ContentHosting entity reference. We do that in this example.
101: * <p>
102: * The key thing we are responsible for is determining if the access is allowed. The natural security set on our private area in ContentHosting
103: * assures that nobody has any access at all to the items there. If we decide to grant access, we setup a security advisor so that ContentHosting's
104: * security check is satisfied.
105: * <p>
106: * The most common way to check security is to encode the context id into the URL, and into the ContentHosting collection structure of your private
107: * content area. Then you can access the context directly from the URL and do a security check. This is what we do in this example.
108: * <p>
109: * An alternate way to handle security is to store meta-data about each item in your application database. Then you need to encode enough information
110: * in the URL to read the item's meta-data. From the meta-data, you can get the ContentHosting reference and security information. We do not show this
111: * in this example.
112: * <p>
113: * If you deploy this sample, you will have a working Access servlet / ContentHosting using application that serves up a single file to anyone
114: * authorized to see it. You can test this by entering this URL:
115: * <ul>
116: * <li>http://<your server dns and port>/access/sampleAccess/content/private/sampleAccess/mercury/test.txt</li>
117: * </ul>
118: * <p>
119: * Note that there are some methods needed for EntityProducer that we don't use here. The Entity Bus will be later enhanced to make this a cleaner
120: * process.
121: * <p>
122: * The key methods are: parseEntityReference...
123: */
124: public class SampleImplAccess implements EntityProducer {
125: /** Our logger. */
126: private static Log M_log = LogFactory
127: .getLog(SampleImplAccess.class);
128:
129: /** The chunk size used when streaming (100k). */
130: protected static final int STREAM_BUFFER_SIZE = 102400;
131:
132: /*************************************************************************************************************************************************
133: * These are really the only part of the example that would, if we had it, better go in the API interface than the implementation class.
134: ************************************************************************************************************************************************/
135:
136: /**
137: * The type string for this application: should not change over time as it may be stored in various parts of persistent entities.
138: */
139: static final String APPLICATION_ID = "sakai:sampleAccess";
140:
141: /** This string starts the references to resources in this service. */
142: static final String REFERENCE_ROOT_NAME = "sampleAccess";
143:
144: static final String REFERENCE_ROOT = "/" + REFERENCE_ROOT_NAME;
145:
146: /*************************************************************************************************************************************************
147: * Abstractions, etc.
148: ************************************************************************************************************************************************/
149:
150: /**
151: * Setup a security advisor.
152: */
153: protected void pushAdvisor() {
154: // setup a security advisor
155: m_securityService.pushAdvisor(new SecurityAdvisor() {
156: public SecurityAdvice isAllowed(String userId,
157: String function, String reference) {
158: return SecurityAdvice.ALLOWED;
159: }
160: });
161: }
162:
163: /**
164: * Remove our security advisor.
165: */
166: protected void popAdvisor() {
167: m_securityService.popAdvisor();
168: }
169:
170: /**
171: * Check security for this entity.
172: *
173: * @param ref
174: * The Reference to the entity.
175: * @return true if allowed, false if not.
176: */
177: protected boolean checkSecurity(Reference ref) {
178: // TODO:
179: return true;
180: }
181:
182: /*************************************************************************************************************************************************
183: * Dependencies
184: ************************************************************************************************************************************************/
185:
186: /** Dependency: ContentHostingService */
187: protected ContentHostingService m_contentHostingService = null;
188:
189: /**
190: * Dependency: ContentHostingService.
191: *
192: * @param service
193: * The ContentHostingService.
194: */
195: public void setContentHostingService(ContentHostingService service) {
196: m_contentHostingService = service;
197: }
198:
199: /** Dependency: EntityManager */
200: protected EntityManager m_entityManager = null;
201:
202: /**
203: * Dependency: EntityManager.
204: *
205: * @param service
206: * The EntityManager.
207: */
208: public void setEntityManager(EntityManager service) {
209: m_entityManager = service;
210: }
211:
212: /** Dependency: ServerConfigurationService. */
213: protected ServerConfigurationService m_serverConfigurationService = null;
214:
215: /**
216: * Dependency: ServerConfigurationService.
217: *
218: * @param service
219: * The ServerConfigurationService.
220: */
221: public void setServerConfigurationService(
222: ServerConfigurationService service) {
223: m_serverConfigurationService = service;
224: }
225:
226: /** Dependency: SecurityService */
227: protected SecurityService m_securityService = null;
228:
229: /**
230: * Dependency: SecurityService.
231: *
232: * @param service
233: * The SecurityService.
234: */
235: public void setSecurityService(SecurityService service) {
236: m_securityService = service;
237: }
238:
239: /** Dependency: SessionManager */
240: protected SessionManager m_sessionManager = null;
241:
242: /**
243: * Dependency: SessionManager.
244: *
245: * @param service
246: * The SessionManager.
247: */
248: public void setSessionManager(SessionManager service) {
249: m_sessionManager = service;
250: }
251:
252: /*************************************************************************************************************************************************
253: * Init and Destroy
254: ************************************************************************************************************************************************/
255:
256: /**
257: * Assure that a collection with this name exists in the container collection: create it if it is missing.
258: *
259: * @param container
260: * The full path of the container collection.
261: * @param name
262: * The collection name to check and create.
263: */
264: protected void assureCollection(String container, String name) {
265: try {
266: m_contentHostingService.getCollection(container + name);
267: } catch (IdUnusedException e) {
268: // create it
269: M_log.info("init: creating root collection");
270:
271: try {
272: ContentCollectionEdit edit = m_contentHostingService
273: .addCollection(container + name);
274: ResourcePropertiesEdit props = edit.getPropertiesEdit();
275:
276: // set the alternate reference root so we get all requests
277: props.addProperty(
278: ContentHostingService.PROP_ALTERNATE_REFERENCE,
279: REFERENCE_ROOT);
280:
281: props.addProperty(ResourceProperties.PROP_DISPLAY_NAME,
282: name);
283:
284: m_contentHostingService.commitCollection(edit);
285: } catch (IdUsedException e2) {
286: M_log.warn("init: creating our root collection: "
287: + e2.toString());
288: } catch (IdInvalidException e2) {
289: M_log.warn("init: creating our root collection: "
290: + e2.toString());
291: } catch (PermissionException e2) {
292: M_log.warn("init: creating our root collection: "
293: + e2.toString());
294: } catch (InconsistentException e2) {
295: M_log.warn("init: creating our root collection: "
296: + e2.toString());
297: }
298: } catch (TypeException e) {
299: M_log.warn("init: checking our root collection: "
300: + e.toString());
301: } catch (PermissionException e) {
302: M_log.warn("init: checking our root collection: "
303: + e.toString());
304: }
305: }
306:
307: /**
308: * Final initialization, once all dependencies are set.
309: */
310: public void init() {
311: // setup a security advisor
312: pushAdvisor();
313:
314: // set the current user to admin
315: Session s = m_sessionManager.getCurrentSession();
316: String oldUserId = null;
317: if (s != null) {
318: oldUserId = s.getUserId();
319: s.setUserId("admin");
320: } else {
321: M_log
322: .warn("init - no SessionManager.getCurrentSession, cannot set to admin user");
323: }
324:
325: try {
326: // register as an entity producer
327: m_entityManager
328: .registerEntityProducer(this , REFERENCE_ROOT);
329:
330: // assume private exists
331: // make sure our root area exists
332: assureCollection("/private/", REFERENCE_ROOT_NAME);
333:
334: // make sure the context collection for mercury in there exists.
335: assureCollection("/private" + REFERENCE_ROOT + "/",
336: "mercury");
337:
338: // check if the content is available
339: String contentRef = "/private" + REFERENCE_ROOT
340: + "/mercury/" + "test.txt";
341:
342: try {
343: m_contentHostingService.getResource(contentRef);
344: } catch (IdUnusedException e) {
345: // create it
346: try {
347: ContentResourceEdit edit = m_contentHostingService
348: .addResource(contentRef);
349: edit.setContent("Hello there!".getBytes());
350: edit.setContentType("text/plain");
351: ResourcePropertiesEdit props = edit
352: .getPropertiesEdit();
353:
354: // set the alternate reference root so we get all requests
355: props
356: .addProperty(
357: ContentHostingService.PROP_ALTERNATE_REFERENCE,
358: REFERENCE_ROOT);
359:
360: props.addProperty(
361: ResourceProperties.PROP_DISPLAY_NAME,
362: "test.txt");
363:
364: m_contentHostingService.commitResource(edit);
365: } catch (PermissionException e2) {
366: M_log.warn("init: creating our content: "
367: + e2.toString());
368: } catch (IdUsedException e2) {
369: M_log.warn("init: creating our content: "
370: + e2.toString());
371: } catch (IdInvalidException e2) {
372: M_log.warn("init: creating our content: "
373: + e2.toString());
374: } catch (InconsistentException e2) {
375: M_log.warn("init: creating our content: "
376: + e2.toString());
377: } catch (ServerOverloadException e2) {
378: M_log.warn("init: creating our content: "
379: + e2.toString());
380: } catch (OverQuotaException e2) {
381: M_log.warn("init: creating our content: "
382: + e2.toString());
383: }
384:
385: M_log.info("init: creating content");
386: } catch (TypeException e) {
387: M_log.warn("init: checking our content: "
388: + e.toString());
389: } catch (PermissionException e) {
390: M_log.warn("init: checking our content: "
391: + e.toString());
392: }
393:
394: M_log.info("init()");
395: } catch (Throwable t) {
396: M_log.warn("init(): ", t);
397: }
398:
399: finally {
400: // clear the security advisor
401: popAdvisor();
402:
403: // restore the current user, if any
404: if ((oldUserId != null) && (s != null)) {
405: s.setUserId(oldUserId);
406: }
407: }
408: }
409:
410: /**
411: * Returns to uninitialized state.
412: */
413: public void destroy() {
414: M_log.info("destroy()");
415: }
416:
417: /*************************************************************************************************************************************************
418: * EntityProducer
419: ************************************************************************************************************************************************/
420:
421: /**
422: * {@inheritDoc}
423: */
424: public boolean parseEntityReference(String reference, Reference ref) {
425: if (reference.startsWith(REFERENCE_ROOT)) {
426: // we will get null, sampleAccess, content, private, sampleAccess, <context>, test.txt
427: // we will store the context, and the ContentHosting reference in our id field.
428: String id = null;
429: String context = null;
430: String[] parts = StringUtil.split(reference,
431: Entity.SEPARATOR);
432:
433: if (parts.length > 5) {
434: context = parts[5];
435: id = "/"
436: + StringUtil.unsplit(parts, 2,
437: parts.length - 2, "/");
438: }
439:
440: ref.set(APPLICATION_ID, null, id, null, context);
441:
442: return true;
443: }
444:
445: return false;
446: }
447:
448: /**
449: * {@inheritDoc}
450: */
451: public HttpAccess getHttpAccess() {
452: return new HttpAccess() {
453: public void handleAccess(HttpServletRequest req,
454: HttpServletResponse res, Reference ref,
455: Collection copyrightAcceptedRefs)
456: throws EntityPermissionException,
457: EntityNotDefinedException,
458: EntityAccessOverloadException,
459: EntityCopyrightException {
460: // decide on security
461: if (!checkSecurity(ref)) {
462: throw new EntityPermissionException(
463: m_sessionManager.getCurrentSessionUserId(),
464: "sampleAccess", ref.getReference());
465: }
466:
467: // isolate the ContentHosting reference
468: Reference contentHostingRef = m_entityManager
469: .newReference(ref.getId());
470:
471: // setup a security advisor
472: pushAdvisor();
473: try {
474: // make sure we have a valid ContentHosting reference with an entity producer we can talk to
475: EntityProducer service = contentHostingRef
476: .getEntityProducer();
477: if (service == null)
478: throw new EntityNotDefinedException(ref
479: .getReference());
480:
481: // get the producer's HttpAccess helper, it might not support one
482: HttpAccess access = service.getHttpAccess();
483: if (access == null)
484: throw new EntityNotDefinedException(ref
485: .getReference());
486:
487: // let the helper do the work
488: access.handleAccess(req, res, contentHostingRef,
489: copyrightAcceptedRefs);
490: } finally {
491: // clear the security advisor
492: popAdvisor();
493: }
494: }
495: };
496: }
497:
498: /**
499: * {@inheritDoc}
500: */
501: public Entity getEntity(Reference ref) {
502: // decide on security
503: if (!checkSecurity(ref))
504: return null;
505:
506: // isolate the ContentHosting reference
507: Reference contentHostingRef = m_entityManager.newReference(ref
508: .getId());
509:
510: // setup a security advisor
511: pushAdvisor();
512: try {
513: // make sure we have a valid ContentHosting reference with an entity producer we can talk to
514: EntityProducer service = contentHostingRef
515: .getEntityProducer();
516: if (service == null)
517: return null;
518:
519: // pass on the request
520: return service.getEntity(contentHostingRef);
521: } finally {
522: // clear the security advisor
523: popAdvisor();
524: }
525: }
526:
527: /**
528: * {@inheritDoc}
529: */
530: public Collection getEntityAuthzGroups(Reference ref, String userId) {
531: // Since we handle security ourself, we won't support anyone else asking
532: return null;
533: }
534:
535: /**
536: * {@inheritDoc}
537: */
538: public String getEntityDescription(Reference ref) {
539: // decide on security
540: if (!checkSecurity(ref))
541: return null;
542:
543: // isolate the ContentHosting reference
544: Reference contentHostingRef = m_entityManager.newReference(ref
545: .getId());
546:
547: // setup a security advisor
548: pushAdvisor();
549: try {
550: // make sure we have a valid ContentHosting reference with an entity producer we can talk to
551: EntityProducer service = contentHostingRef
552: .getEntityProducer();
553: if (service == null)
554: return null;
555:
556: // pass on the request
557: return service.getEntityDescription(contentHostingRef);
558: } finally {
559: // clear the security advisor
560: popAdvisor();
561: }
562: }
563:
564: /**
565: * {@inheritDoc}
566: */
567: public ResourceProperties getEntityResourceProperties(Reference ref) {
568: // decide on security
569: if (!checkSecurity(ref))
570: return null;
571:
572: // isolate the ContentHosting reference
573: Reference contentHostingRef = m_entityManager.newReference(ref
574: .getId());
575:
576: // setup a security advisor
577: pushAdvisor();
578: try {
579: // make sure we have a valid ContentHosting reference with an entity producer we can talk to
580: EntityProducer service = contentHostingRef
581: .getEntityProducer();
582: if (service == null)
583: return null;
584:
585: // pass on the request
586: return service
587: .getEntityResourceProperties(contentHostingRef);
588: } finally {
589: // clear the security advisor
590: popAdvisor();
591: }
592: }
593:
594: /**
595: * {@inheritDoc}
596: */
597: public String getEntityUrl(Reference ref) {
598: return m_serverConfigurationService.getAccessUrl()
599: + ref.getReference();
600: }
601:
602: /**
603: * {@inheritDoc}
604: */
605: public String getLabel() {
606: return null;
607: }
608:
609: /**
610: * {@inheritDoc}
611: */
612: public String merge(String siteId, Element root,
613: String archivePath, String fromSiteId, Map attachmentNames,
614: Map userIdTrans, Set userListAllowImport) {
615: return null;
616: }
617:
618: /**
619: * {@inheritDoc}
620: */
621: public String archive(String siteId, Document doc, Stack stack,
622: String archivePath, List attachments) {
623: return null;
624: }
625:
626: /**
627: * {@inheritDoc}
628: */
629: public boolean willArchiveMerge() {
630: return false;
631: }
632: }
|