View Javadoc

1   /*
2    * Copyright 2007-2008 Hippo
3    *
4    * Licensed under the Apache License, Version 2.0 (the  "License"); 
5    * you may not use this file except in compliance with the License. 
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" 
12   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
13   * See the License for the specific language governing permissions and 
14   * limitations under the License.
15   */
16  package nl.hippo.portal.cms;
17  
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.nio.ByteBuffer;
21  import java.nio.channels.Channels;
22  import java.nio.channels.ReadableByteChannel;
23  import java.nio.channels.WritableByteChannel;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Locale;
28  
29  import javax.servlet.http.HttpServletRequest;
30  import javax.servlet.http.HttpServletResponse;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  
35  import nl.hippo.client.api.content.Document;
36  import nl.hippo.portal.cms.CMSRequestContext.UrlType;
37  import nl.hippo.portal.cms.site.SiteMap;
38  import nl.hippo.portal.cms.site.SiteMapItem;
39  import nl.hippo.portal.domain.model.User;
40  import nl.hippo.portal.locking.RequestLockManager;
41  import nl.hippo.portal.registry.Registration;
42  
43  /***
44   * @version $Id: CMSApplicationRegistryImpl.java 14738 2008-11-17 10:46:54Z ddam $
45   *
46   */
47  public class CMSApplicationRegistryImpl implements CMSApplicationRegistry
48  {
49      private static final Log log = LogFactory.getLog(CMSApplicationRegistryImpl.class);
50      
51      private static final Class[] REGISTRATION_TYPES = {CMSApplicationRegistration.class};
52      
53      private static final int RESOURCE_BUFFER_SIZE = 1024 * 16;
54      
55      private RequestLockManager lockManager;
56      
57      private HashMap apps = new HashMap();
58      private HashMap domains = new HashMap();
59      private HashMap serverDomainMap = new HashMap();
60      
61      public CMSApplicationRegistryImpl()
62      {
63      }
64  
65      public CMSApplicationRegistryImpl(RequestLockManager lockManager)
66      {
67          this.lockManager = lockManager;
68      }
69  
70      /* (non-Javadoc)
71       * @see nl.hippo.portal.registry.RegistrationAware#getRegistrationTypes()
72       */
73      public Class[] getRegistrationTypes()
74      {
75          return REGISTRATION_TYPES;
76      }
77  
78      /* (non-Javadoc)
79       * @see nl.hippo.portal.registry.Registration#getRegistryName()
80       */
81      public String getRegistryName()
82      {
83          return SERVICE_NAME;
84      }
85  
86      /* (non-Javadoc)
87       * @see nl.hippo.portal.registry.Registration#registered()
88       */
89      public void registered()
90      {
91      }
92  
93      /* (non-Javadoc)
94       * @see nl.hippo.portal.registry.Registration#unregistered()
95       */
96      public void unregistered()
97      {
98      }
99      
100     /* (non-Javadoc)
101      * @see nl.hippo.portal.registry.RegistrationAware#available(nl.hippo.portal.registry.Registration)
102      */
103     public void available(Registration registration)
104     {
105         if ( registration instanceof CMSApplicationRegistration )
106         {
107             CMSApplicationRegistration cmsApp = (CMSApplicationRegistration)registration;
108             CMSApplicationImpl app = (CMSApplicationImpl)apps.get(cmsApp.getApplicationName());
109             if ( app != null )
110             {
111                 app.available(cmsApp);
112             }
113             else
114             {
115                 log.error("No CMSApplication defined for CMSApplicationRegistration named "+cmsApp.getApplicationName());
116                 // simply ignore the registration
117             }
118         }
119     }
120 
121     /* (non-Javadoc)
122      * @see nl.hippo.portal.registry.RegistrationAware#unavailable(nl.hippo.portal.registry.Registration)
123      */
124     public void unavailable(Registration registration)
125     {
126         if ( registration instanceof CMSApplicationRegistration )
127         {
128             CMSApplicationRegistration cmsApp = (CMSApplicationRegistration)registration;
129             CMSApplicationImpl app = (CMSApplicationImpl)apps.get(cmsApp.getApplicationName());
130             if ( app != null )
131             {
132                 app.available(null);
133             }
134             else
135             {
136                 // simply ignore the unregistration
137             }
138         }
139     }
140 
141     public void setDomains(List domains)
142     {
143         Iterator iter = domains.iterator();
144         while ( iter.hasNext() )
145         {
146             CMSDomainImpl domain = (CMSDomainImpl)iter.next();            
147             this.domains.put(domain.getName(), domain);
148             this.serverDomainMap.put(domain.getFullName(), domain);
149         }
150     }
151 
152     public void setApplications(List apps)
153     {
154         Iterator iter = apps.iterator();
155         while ( iter.hasNext() )
156         {
157             CMSApplicationImpl app = (CMSApplicationImpl)iter.next();            
158             this.apps.put(app.getName(), app);
159         }
160     }
161     
162     protected CMSDomainImpl getDomain(CMSPortalURL portalURL){
163     	return (CMSDomainImpl) serverDomainMap.get(CMSDomainImpl.buildFullName(portalURL.getHost().getName(),portalURL.getHost().getPort()));
164     }
165     
166     public CMSSite resolveSite(CMSUser cmsUser, CMSPortalURL portalURL, Locale locale){
167     	CMSDomainImpl domain = getDomain(portalURL);
168         return resolveSiteImpl(domain,cmsUser, portalURL, locale);
169     }
170     
171     public CMSSiteImpl resolveSiteImpl(CMSDomainImpl domain,CMSUser cmsUser, CMSPortalURL portalURL, Locale locale){
172     	CMSSiteImpl site = null;
173         if ( domain != null ){
174             
175             if ( portalURL.getSiteName() != null ) {
176                 site = (CMSSiteImpl)domain.getSite(portalURL.getSiteName());
177             }
178             else if (domain.getDefaultSite() != null) {
179                 site = (CMSSiteImpl)domain.getSite(domain.getDefaultSite());
180             }
181         }
182         return site;
183     }
184     
185     protected SiteMapItem resolveSiteMapItem(CMSSiteImpl site, String path, CMSUser user, CMSPortalURL originalPortalUrl, Locale locale, boolean exactMatch, boolean usePortalUrl){
186     	SiteMapItem smi = findSiteMapItem(site, path, exactMatch );
187     	if (smi != null && !isSiteMapItemValid(smi, site, user, originalPortalUrl, locale,usePortalUrl)){
188     		smi = null;
189     	}
190     	return smi;
191     }
192     
193     protected boolean isSiteMapItemValid(SiteMapItem smi, CMSSiteImpl site, CMSUser user, CMSPortalURL portalURL, Locale locale, boolean usePortalUrl){
194     	String sitePart = getSiteUrlPart(portalURL, site);
195     	if (!usePortalUrl){
196         	portalURL = portalURL.createPortalURL(portalURL.getBasePath()+sitePart+smi.getPath(),true);
197     	}
198     	CMSRequestContextImpl rc = createContext(site, user, portalURL, portalURL, smi, locale);
199     	return rc.getSrc() == null || (rc.getSrc() != null  && rc.getMetadata() != null);
200     }
201     
202     /***
203      * 
204      * @param portalUrl the original portal url
205      * @param site the site to be used to resolve the context
206      * @param user user
207      * @param path the sitemap path to be resolved to a context
208      * @param locale locale
209      * @param usePortalUrl true, if the original portal url's sitemap postfix and parameters should be used
210      * @return a status object containing information about which items could be resolved
211      */
212     protected ContextResolveStatus resolveContextForSite(CMSPortalURL portalUrl, CMSSiteImpl site, CMSUser user, String path, Locale locale, boolean usePortalUrl){
213     	ContextResolveStatus resolveStatus = new ContextResolveStatus();
214     	SiteMapItem smi = null;
215     	if ( site.isAvailable() )
216         {
217             site.checkRefreshData();
218 
219         	
220             if ( path != null )
221             {
222                 smi = resolveSiteMapItem(site, path, user, portalUrl, locale, false, usePortalUrl );
223             }
224             else if ( site.getDefaultPath() != null )
225             {
226                 smi = resolveSiteMapItem(site, site.getDefaultPath(), user, portalUrl, locale, true, usePortalUrl );
227             }
228             resolveStatus.setRequestedPage(smi);
229             if (smi == null){
230                 // no sitemap item resolved, get the page not found item, if configured
231                 smi = resolveSiteMapItem(site, site.getPageNotFoundPath(), user, portalUrl, locale, true, usePortalUrl );
232                 resolveStatus.setPage404(smi);
233             }
234             if ( smi != null ){            	
235                 // check whether a login is required for the resolved sitemap item
236             	if ( (user == null || user.getPrincipal() == null) && isLoginRequired(site,smi)){
237             		if (site.getRequiredLoginPath() != null){
238                         smi = resolveSiteMapItem(site, site.getRequiredLoginPath(), user, portalUrl, locale, true, usePortalUrl );
239             		} else {
240             			smi = null;
241             		}
242             		resolveStatus.setLoginPage(smi);                    
243             	} else {
244                     // user logged in, now check whether the user needs to accept the terms of use
245                     user = loadUser(site, user);
246                     if (user.getUser() != null && user.getPrincipal() != null && site.getPageAcceptTermsOfUse() != null && !user.getUser().isTermsOfUseAccepted())
247                     {
248                         smi = resolveSiteMapItem(site, site.getPageAcceptTermsOfUse(), user, portalUrl, locale, true, usePortalUrl );
249                         resolveStatus.setOtherPage(smi); 
250                     }
251                 }
252             	
253             	// the CMSRequestContext is not yet created, but a sitemap item has been resolved.
254                 if (smi != null)
255                 {
256                 	
257                 	String sitePart = getSiteUrlPart(portalUrl, site);
258                     
259                 	CMSPortalURL newPortalUrl = null;
260                 	// use original portal url if it could be resolved
261                 	if (usePortalUrl && resolveStatus.requestedPageUsed){
262                 		newPortalUrl=portalUrl;
263                 	} else {
264                 		// otherwise create a new portal url based on the resolved sitemap item
265                 		newPortalUrl=portalUrl.createPortalURL(portalUrl.getBasePath()+sitePart+smi.getPath(),usePortalUrl);
266                 	}
267                     
268                     resolveStatus.context=createContext(site, user, newPortalUrl, portalUrl, smi, locale);    
269                 }
270                 
271             }
272         }
273     	return resolveStatus;
274     }
275     
276     private boolean isDefaultSite(CMSDomainImpl domain, CMSSite site){
277     	return site != null && domain != null && domain.getDefaultSite() != null && site.getName() != null && site.getName().equals(domain.getDefaultSite());
278     }
279     
280     public CMSRequestContext resolve(CMSUser user, CMSPortalURL portalURL, Locale locale)
281     {
282     	logResolveInfo(user,portalURL,locale);
283     	CMSDomainImpl domain = getDomain(portalURL);
284         ContextResolveStatus resolveStatus = new ContextResolveStatus();
285 
286     	if (domain != null){
287             CMSSiteImpl site = resolveSiteImpl(domain,user,portalURL,locale);
288             if (site != null){
289             	resolveStatus = resolveContextForSite(portalURL,site,user,portalURL.getSiteMapPath(),locale,true);
290             }
291             
292             if ( resolveStatus.context == null && domain.getDefaultSite() != null && !isDefaultSite(domain, site)){
293             	site = (CMSSiteImpl) domain.getSite(domain.getDefaultSite());
294             	if (site != null){
295             		// process status of attempt to resolve item in the non-default site
296                 	if (resolveStatus.page404NotFound){
297             			// 404 page not found in non-default site, try to look up 404 page in default site
298                 		if (site.getPageNotFoundPath() != null){
299                     		resolveStatus = resolveContextForSite(portalURL,site,user,site.getPageNotFoundPath(),locale,false);
300                 		}
301                 	} else if (resolveStatus.loginPageNotFound){
302             			// login page not found in non-default site, try to look up login page in default site
303                 		if (site.getRequiredLoginPath() != null){
304                 			resolveStatus = resolveContextForSite(portalURL,site,user,site.getRequiredLoginPath(),locale,false);
305                 		}
306                 	}
307                 	
308                 	// no item found at all, try the default item in the default site as a last resort
309                 	if (resolveStatus.context == null && site.getDefaultPath() != null){
310                 		resolveStatus = resolveContextForSite(portalURL,site,user,site.getDefaultPath(),locale,false);
311                 	}
312             	}
313             }
314             
315             if (resolveStatus.context != null){
316                 setUrlType(resolveStatus.context, site);
317             }
318     	}
319 
320         return resolveStatus.context;
321     }
322     
323     protected static void logResolveInfo(CMSUser user, CMSPortalURL portalURL, Locale locale){
324     	if (log.isDebugEnabled())
325         {
326             StringBuffer b = new StringBuffer("resolve(user :");
327             b.append(user.getPrincipal() != null ? user.getPrincipal().getName() : "null");
328             b.append(", url: ").append(portalURL.toString());
329             b.append(", locale: ").append(locale != null ? locale.toString() : "null");
330             b.append(')');
331             log.debug(b.toString());
332         }
333     }
334     
335     private void setUrlType(CMSRequestContextImpl context, CMSSiteImpl site){
336     	CMSPortalURL portalUrl = context.getPortalURL();
337     	if (portalUrl != null){
338         	if (site.getPageNotFoundPath() != null && site.getPageNotFoundPath().equals(portalUrl.getSiteMapPath())){
339         		context.setUrlType(UrlType.PAGENOTFOUND);
340         	} else if (site.getPageAcceptTermsOfUse() != null && site.getPageAcceptTermsOfUse().equals(portalUrl.getSiteMapPath())) {
341         		context.setUrlType(UrlType.ACCEPT_TERMS);
342         	} else if (site.getDefaultPath() != null && site.getDefaultPath().equals(portalUrl.getSiteMapPath())) {
343         		context.setUrlType(UrlType.DEFAULT);
344         	} else if (site.getRequiredLoginPath() != null && site.getRequiredLoginPath().equals(portalUrl.getSiteMapPath())) {
345         		context.setUrlType(UrlType.LOGIN);
346         	} else {
347         		context.setUrlType(UrlType.NORMAL);
348         	}
349     	}
350     }
351     
352     private String getSiteUrlPart(CMSPortalURL portalURL, CMSSite site){
353     	return (portalURL.getSiteName() == null && portalURL.usesDefaultSite() ? "" : "/"+site.getName());
354     }
355     
356     private boolean isLoginRequired(CMSSiteImpl site, SiteMapItem smi) {
357         if ( site.getPublicPaths() != null ) {
358             String smiPath = smi.getPath();
359             String[] publicPaths = site.getPublicPaths();
360             for (int i = 0; i < publicPaths.length; i++ ) {
361                 if (publicPaths[i] != null && smiPath.startsWith(publicPaths[i])) {
362                     return false;
363                 }
364             }
365         }
366         return true;
367     }
368     
369     private SiteMapItem findSiteMapItem(CMSSiteImpl site, String path, boolean exact)
370     {
371         if ( log.isDebugEnabled() )
372         {
373             log.debug("findSiteMapItem("+site.getFullName()+","+path+","+exact+")");
374         }
375         SiteMapItem smi = null;
376         List siteMaps = site.getSiteMaps();
377         if ( siteMaps != null )
378         {
379             for ( int i = 0, size = siteMaps.size(); i < size; i++ )
380             {
381                 SiteMap sm = (SiteMap)siteMaps.get(i);
382                 if ( log.isDebugEnabled())
383                 {
384                     log.debug("  find in SiteMap: "+sm.getName());
385                 }
386                 if ( exact )
387                 {
388                     smi = sm.getByPath(path);
389                 }
390                 else
391                 {
392                     smi = sm.matchPath(path);
393                 }
394                 if ( smi != null )
395                 {
396                     if ( !smi.isLinkable() )
397                     {
398                         smi = null;
399                     }
400                     else
401                     {
402                         break;
403                     }
404                 }
405             }
406         }
407         if ( log.isDebugEnabled() )
408         {
409             if ( smi == null )
410             {
411                 log.debug("  find result: null");
412             }
413             else
414             {
415                 log.debug("  find result: "+smi.getPath());
416             }
417         }
418         return smi;
419     }
420     
421     private CMSUser loadUser(CMSSiteImpl site, CMSUser user) {
422         if ( user.getUser() == null && user.getPrincipal() != null && site.getUserService() != null )
423         {
424             ClassLoader cl = Thread.currentThread().getContextClassLoader();
425             try
426             {
427                 String userName = user.getPrincipal().getName();
428                 Thread.currentThread().setContextClassLoader(site.getUserService().getClass().getClassLoader());
429                 User usr = site.getUserService().getUser(userName);
430                 if (usr != null )
431                 {
432                     user = new CMSUserWrapper(user, usr);
433                 }
434             }
435             finally
436             {
437                 Thread.currentThread().setContextClassLoader(cl);
438             }
439         }
440         return user;
441     }    
442     
443     
444     private CMSRequestContextImpl createContext(CMSSiteImpl site, CMSUser user, CMSPortalURL portalURL, CMSPortalURL originalURL, SiteMapItem smi, Locale locale)
445     {
446         user = loadUser(site, user);
447         return new CMSRequestContextImpl(site,user,portalURL,originalURL,smi,locale);
448     }
449 
450     public void serveResource(HttpServletRequest request, HttpServletResponse response) throws IOException
451     {        
452         CMSDomainImpl domain = (CMSDomainImpl)serverDomainMap.get(CMSDomainImpl.buildFullName(request.getServerName(),request.getServerPort()));
453         if ( domain == null )
454         {
455             response.sendError(HttpServletResponse.SC_FORBIDDEN);
456             return;
457         }
458         String resourcePath = request.getPathInfo();
459         if ( resourcePath == null )
460         {
461             response.sendError(HttpServletResponse.SC_FORBIDDEN);
462             return;
463         }
464         // strip off leading slash
465         resourcePath = resourcePath.substring(1);
466         int slash = resourcePath.indexOf('/');
467         if (slash == -1)
468         {
469             response.sendError(HttpServletResponse.SC_FORBIDDEN);
470             return;
471         }
472         CMSSiteImpl site = (CMSSiteImpl)domain.getSite(resourcePath.substring(0, slash));
473         if ( site == null )
474         {
475             response.sendError(HttpServletResponse.SC_NOT_FOUND);
476             return;
477         }
478         if ( resourcePath.length() < slash+1 )
479         {
480             response.sendError(HttpServletResponse.SC_FORBIDDEN);
481             return;
482         }
483         if ( site.getRequiredLoginPath() != null && request.getUserPrincipal() == null )
484         {
485             response.sendError(HttpServletResponse.SC_FORBIDDEN);
486             return;
487         }
488         // TODO: check userInRole
489         
490         resourcePath = resourcePath.substring(slash+1);
491         Document resource = site.getCMSService(CMSService.RESOURCE).get(resourcePath);
492         if ( resource == null )
493         {
494             response.sendError(HttpServletResponse.SC_NOT_FOUND);
495             return;
496         }
497         String contentType = resource.getMetadata().getContentType();
498         if ( contentType == null )
499         {
500             response.sendError(HttpServletResponse.SC_BAD_REQUEST);
501             return;
502         }
503 
504         InputStream src = resource.getContent().getResponseAsStream();
505         ReadableByteChannel readChannel = null;
506         WritableByteChannel writeChannel = null;
507         try 
508         {
509             if (src != null) 
510             {
511                 response.setContentType(contentType);
512 //              response.setContentLength(resource.getMetadata().getContentLength());
513                 ByteBuffer buffer = ByteBuffer.allocateDirect(RESOURCE_BUFFER_SIZE);
514                 readChannel = Channels.newChannel(src);
515                 writeChannel = Channels.newChannel(response.getOutputStream());
516                 while (readChannel.read(buffer) != -1 && writeChannel.isOpen())
517                 {
518                     buffer.flip();
519                     writeChannel.write(buffer);
520                     buffer.clear();
521                 }
522 
523                 writeChannel.close();
524                 writeChannel = null;
525                 readChannel.close();
526                 readChannel = null;
527                 src.close();
528                 src = null;
529                 response.flushBuffer();
530             } 
531             else 
532             {
533                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
534             }
535         } 
536         catch (Exception e)
537         {
538             if (src != null) 
539             {
540                 try 
541                 {
542                     src.close();
543                 } 
544                 catch (Exception e2) 
545                 {
546                     // ignore
547                 }
548             }
549             if (writeChannel != null)
550             {
551                 try 
552                 {
553                     writeChannel.close();
554                 } 
555                 catch (Exception e2)
556                 {
557                     // ignore
558                 }
559             }
560             if (readChannel != null)
561             {
562                 try 
563                 {
564                     readChannel.close();
565                 } 
566                 catch (Exception e2) 
567                 {
568                     // ignore
569                 }
570             }
571             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
572         }
573     }
574     
575     protected static class ContextResolveStatus {
576     	
577     	private boolean page404NotFound;
578     	private boolean loginPageNotFound;
579     	private boolean requestedPageUsed;
580     	
581     	private CMSRequestContextImpl context;
582     	
583     	public void setPage404(SiteMapItem smi){
584     		requestedPageUsed=false;
585     		page404NotFound= (smi == null);
586     	}
587     	
588     	public void setLoginPage(SiteMapItem smi){
589     		requestedPageUsed=false;
590     		loginPageNotFound= (smi == null);
591     	}
592     	
593     	public void setRequestedPage(SiteMapItem smi){
594     		requestedPageUsed = (smi != null);
595     	}    	
596     	
597     	public void setOtherPage(SiteMapItem smi){
598     		requestedPageUsed=false;
599     	}
600     }
601     
602 }