View Javadoc

1   /*
2    * Copyright 2000-2004 The Apache Software Foundation.
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" BASIS,
12   * 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  
17  package org.apache.jetspeed.services.profiler;
18  
19  //java.util
20  import java.util.Iterator;
21  import java.util.LinkedList;
22  import java.util.List;
23  import java.util.Locale;
24  import java.util.Vector;
25  
26  import javax.servlet.ServletConfig;
27  
28  import org.apache.commons.lang.SerializationUtils;
29  import org.apache.jetspeed.capability.CapabilityMap;
30  import org.apache.jetspeed.capability.CapabilityMapFactory;
31  import org.apache.jetspeed.om.profile.BasePSMLDocument;
32  import org.apache.jetspeed.om.profile.Control;
33  import org.apache.jetspeed.om.profile.Controller;
34  import org.apache.jetspeed.om.profile.PSMLDocument;
35  import org.apache.jetspeed.om.profile.Portlets;
36  import org.apache.jetspeed.om.profile.Profile;
37  import org.apache.jetspeed.om.profile.ProfileException;
38  import org.apache.jetspeed.om.profile.ProfileLocator;
39  import org.apache.jetspeed.om.profile.QueryLocator;
40  import org.apache.jetspeed.om.profile.Skin;
41  import org.apache.jetspeed.om.profile.psml.PsmlControl;
42  import org.apache.jetspeed.om.profile.psml.PsmlController;
43  import org.apache.jetspeed.om.profile.psml.PsmlPortlets;
44  import org.apache.jetspeed.om.profile.psml.PsmlSkin;
45  import org.apache.jetspeed.om.security.Group;
46  import org.apache.jetspeed.om.security.GroupRole;
47  import org.apache.jetspeed.om.security.JetspeedUser;
48  import org.apache.jetspeed.om.security.Role;
49  import org.apache.jetspeed.services.JetspeedSecurity;
50  import org.apache.jetspeed.services.PortalToolkit;
51  import org.apache.jetspeed.services.Profiler;
52  import org.apache.jetspeed.services.PsmlManager;
53  import org.apache.jetspeed.services.ServiceHelper;
54  import org.apache.jetspeed.services.customlocalization.CustomLocalizationService;
55  import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
56  import org.apache.jetspeed.services.logging.JetspeedLogger;
57  import org.apache.jetspeed.services.rundata.JetspeedRunData;
58  import org.apache.jetspeed.util.MimeType;
59  import org.apache.jetspeed.util.ServiceUtil;
60  import org.apache.turbine.services.InitializationException;
61  import org.apache.turbine.services.TurbineBaseService;
62  import org.apache.turbine.services.TurbineServices;
63  import org.apache.turbine.services.localization.LocalizationService;
64  import org.apache.turbine.services.resources.ResourceService;
65  import org.apache.turbine.services.resources.TurbineResources;
66  import org.apache.turbine.util.DynamicURI;
67  import org.apache.turbine.util.RunData;
68  /***
69   * <p>This is an implementation of the <code>Profiler</code> interface.
70   *
71   * This implementation maps requests to profiles (PSML resources) based on
72   * request parameters, requesting deviced capabilities, and the device's
73   * language. </p>
74   * <p>This service expects these properties to be set for correct operation:
75   * <dl>
76   *    <dt>root</dt><dd>The webapp rel. path to the root profiling directory</dd>
77   *    <dt>resource.default</dt><dd>The default resource filename</dd>
78   *    <dt>resource.ext</dt><dd>The default resource filename extension</dd>
79   *    <dt>security</dt><dd>Use security flag</dd>
80   *    <dt>fallback.language</dt><dd>Use language configuration flag</dd>
81   *    <dt>fallback.country</dt><dd>Use country configuration flag</dd>
82   *    <dt>fallback.to.root</dt><dd>Continue falling back past media type flag</dd>
83   *
84   * </dl>
85   * </p>
86   *
87   * @author <a href="mailto:david@bluesunrise.com">David Sean Taylor</a>
88   * @author <a href="mailto:sgala@hisitech.com">Santiago Gala</a>
89   * @author <a href="mailto:morciuch@apache.org">Mark Orciuch</a>
90   * @version $Id: JetspeedProfilerService.java,v 1.56 2004/02/23 03:35:24 jford Exp $
91   */
92  
93  public class JetspeedProfilerService  extends TurbineBaseService
94      implements ProfilerService
95  {
96      /***
97       * Static initialization of the logger for this class
98       */    
99      private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(JetspeedProfilerService.class.getName());
100     
101     // configuration keys
102     private final static String CONFIG_RESOURCE_DEFAULT = "resource.default";
103     private final static String CONFIG_RESOURCE_EXT     = "resource.ext";
104     private final static String CONFIG_SECURITY         = "security";
105     private final static String CONFIG_ROLE_FALLBACK    = "rolefallback";
106     private final static String CONFIG_NEWUSER_TEMPLATE  = "newuser.template";
107     private final static String CONFIG_NEWUSER_MEDIA     = "newuser.media_types";
108     private final static String CONFIG_FALLBACK_LANGUAGE = "fallback.language";
109     private final static String CONFIG_FALLBACK_COUNTRY = "fallback.country";
110     private final static String CONFIG_FALLBACK_TO_ROOT = "fallback.to.root";
111     private final static String CONFIG_ROLE_MERGE = "rolemerge";
112     private final static String CONFIG_ROLE_MERGE_CONTROL = "rolemerge.control";
113     private final static String CONFIG_ROLE_MERGE_CONTROLLER = "rolemerge.controller";
114 
115     // default configuration values
116     private final static String DEFAULT_CONFIG_RESOURCE_DEFAULT = "default";
117     private final static String DEFAULT_CONFIG_RESOURCE_EXT = ".psml";
118     private final static boolean DEFAULT_CONFIG_SECURITY = false;
119     private final static boolean DEFAULT_CONFIG_ROLE_FALLBACK = true;
120     private final static String DEFAULT_CONFIG_NEWUSER_TEMPLATE = null;
121     private final static String [] DEFAULT_CONFIG_NEWUSER_MEDIA =
122     { "html", "wml" };
123     private final static String DEFAULT_CONFIG_ROLE_MERGE_CONTROL = "TabControl";
124     private final static String DEFAULT_CONFIG_ROLE_MERGE_CONTROLLER = "TabController";
125 
126     private final static String PATH_EXTENSION_DELIMITER = ".";
127     // messages
128     private final static String MSG_MISSING_PARAMETER =
129         "JetspeedProfilerService initialization failed. Missing parameter:";
130 
131     // pluggable Locator and Profile classes
132     private Class profileClass = null;
133     private Class locatorClass = null;
134     
135 
136     // configuration parameters
137     String root;                   // the root psml resource directory
138     String resourceDefault;        // the default name for a resource
139     String resourceExt;            // the default extension for a resource
140     String rolemergeControl;       // the default control used with merged role profiles
141     String rolemergeController;    // the default controller used with merged role profiles
142 
143     // MODIFIED: A. Kempf
144     String newUserTemplate = DEFAULT_CONFIG_NEWUSER_TEMPLATE;
145 
146     boolean useSecurity = false;   // use security features
147     boolean useRoleFallback = true;
148     boolean useFallbackLanguage = true;
149     boolean useFallbackCountry = true;
150     boolean useFallbackToRoot = false;
151     boolean useRoleMerge = false;
152 
153     String mediaTypes[] = null;
154 
155     /***
156      * This methode creates a wml profile and a html profile
157      * for a new user
158      * --------------------------------------------------------------------------
159      * last modified: 10/31/01
160      * Andreas Kempf, Siemens ICM S CP PE, Munich
161      * mailto: A.Kempf@web.de
162      */
163     public Profile createProfile(RunData data, Profile profile)
164             throws ProfileException
165     {
166         Profile current = null;
167         CapabilityMap map;
168 
169         if (data == null)
170         {
171             map = CapabilityMapFactory.getDefaultCapabilityMap();
172         }
173         else
174         {
175             map = ((JetspeedRunData)data).getCapability();
176         }
177         String mediaType = getMediaType(data, map);
178 
179         // if template is null then use role-based psml
180         if (newUserTemplate == null)
181             return current;
182 
183         if (mediaTypes != null)
184         {
185             Profile dummy;
186             for (int ix=0; ix < mediaTypes.length; ix++)
187             {
188                 dummy = createProfile(data, profile, mediaTypes[ix], newUserTemplate);
189                 if (mediaTypes[ix].equalsIgnoreCase(mediaType))
190                     current = dummy;
191             }
192         }
193         return current;
194     }
195 
196     // --------------------------------------------------------------------------
197 
198     /***
199      * This is the early initialization method called by the
200      * Turbine <code>Service</code> framework
201      * @param conf The <code>ServletConfig</code>
202      * @exception throws a <code>InitializationException</code> if the service
203      * fails to initialize
204      */
205     public synchronized void init(ServletConfig conf) throws InitializationException
206     {
207         
208         // already initialized
209         if (getInit()) return;
210 
211         try
212         {
213             initConfiguration();
214         }
215         catch (Exception e)
216         {
217             logger.error("Profiler: Failed to load Service ", e);
218         }
219 
220         // initialization done
221         setInit(true);
222 
223      }
224 
225     /***
226      * This is the shutdown method called by the
227      * Turbine <code>Service</code> framework
228      */
229     public void shutdown()
230     {
231     }
232 
233     /***
234      *  get the Profile object using the Rundata state and capability map
235      *  this is the mapping functionality of the profiler
236      *
237      * @param rundata the rundata object for the current request
238      * @param cm the <code>CapabilityMap</code> of the current requesting device
239      * @return a new Profile object
240      */
241     public Profile getProfile(RunData data, CapabilityMap cm)
242         throws ProfileException
243     {
244         JetspeedRunData rundata = (JetspeedRunData)data;
245         Profile profile = fallbackProfile(rundata, cm);
246         if (null == profile && useRoleFallback)
247         {
248             Vector profiles = new Vector();
249             JetspeedUser user = rundata.getJetspeedUser();
250             if (user != null)
251             {
252                 try
253                 {
254                     String paramRole = rundata.getParameters().getString(Profiler.PARAM_ROLE);
255                     Iterator groupRoles = JetspeedSecurity.getRoles(user.getUserName());
256                     if (groupRoles != null)
257                     {
258                         while (groupRoles.hasNext())
259                         {
260                             // note: this is an unordered list. will need to change db schema to order it
261                             GroupRole gr = (GroupRole) groupRoles.next();
262                             rundata.getParameters().setString( Profiler.PARAM_ROLE, gr.getRole().getName() );
263                             profile = fallbackProfile(rundata, cm);
264                             if (profile != null)
265                             {
266                                 profiles.add(profile);
267                             }
268                             rundata.getParameters().remove(Profiler.PARAM_ROLE);
269                         }
270                         profile = mergeRoleProfiles(data, profiles);
271 
272                         // If something went wrong with merging, attempt another fallback
273                         if (profile == null)
274                         {
275                             profile = fallbackProfile(rundata, cm);
276                         }
277                     }
278                     
279                     rundata.getParameters().setString(Profiler.PARAM_ROLE, paramRole);
280                 }
281                 catch (Exception e)
282                 {
283                     logger.error( "Error getting profile", e );
284                     throw new ProfileException(e.toString());
285                 }
286             }
287         }
288         return profile;
289     }
290 
291     /***
292      * Merge role profiles to create default profile. Resulting psml will be a set of
293      * tabs. If role's psml is a tab control, each tab is placed in the resulting psml
294      * as is. Otherwise, a new tab is created and psml is placed there. In this case,
295      * tab name will be derived from role's name. For example, if role name is "news",
296      * the resulting profile name will be "News Home".
297      *
298      * @param data
299      * @param profiles Vector of profiles for all roles user is part of
300      * @return Merged profile
301      * @exception Exception
302      */
303     private Profile mergeRoleProfiles(RunData data, Vector profiles)
304         throws Exception
305     {
306         Profile result = null;
307         // If merge feature is not turned on, return
308         // profile for the first role (if any)
309         if (!this.useRoleMerge)
310         {
311             if (profiles.size() > 0)
312             {
313                 result = (Profile) profiles.get(0);
314             }
315         }
316         // Proceed with merging all profiles
317         else if (profiles.size() > 0)
318         {
319             try
320             {
321                 // Create an empty portlet container
322                 Portlets portlets = new PsmlPortlets();
323                 Control control = new PsmlControl();
324                 control.setName(this.rolemergeControl);
325                 portlets.setControl(control);
326                 Controller controller = new PsmlController();
327                 controller.setName(this.rolemergeController);
328                 portlets.setController(controller);
329 
330                 // Set the skin
331                 Skin skin = new PsmlSkin();
332                 skin.setName(PortalToolkit.getSkin((String) null).getName());
333                 portlets.setSkin(skin);
334 
335                 String mediaType = null;
336 
337                 // Process each role profile
338                 int paneCount = 0;
339                 for (Iterator it = profiles.iterator(); it.hasNext(); )
340                 {
341                     Profile roleProfile = (Profile)it.next();
342                     mediaType = mediaType == null ? roleProfile.getMediaType() : mediaType;
343                     Profile tmpProfile = (Profile) roleProfile.clone();
344                     Portlets tmpPortlets = tmpProfile.getDocument().getPortlets();
345 
346                     // If topmost control is a tab control, then add each tab to the container
347                     Control paneControl = tmpPortlets.getControl();
348                     if (paneControl != null && paneControl.getName().equals(this.rolemergeControl))
349                     {
350                         for (int i = 0; i < tmpPortlets.getPortletsCount(); i++)
351                         {
352                             Portlets pane = tmpPortlets.getPortlets(i);
353                             pane.setLayout(null);                            
354                             portlets.addPortlets(pane);
355                             paneCount++;
356                         }
357                     }
358                     // Otherwise, add the contents of profile as a pane
359                     else
360                     {
361                         if (tmpPortlets.getTitle() == null)
362                         {
363                             String title = org.apache.turbine.util.StringUtils.firstLetterCaps(roleProfile.getRoleName());
364                             tmpPortlets.setTitle(title + " Home");
365                         }
366                         tmpPortlets.setLayout(null);
367                         portlets.addPortlets(tmpPortlets);
368                         paneCount++;
369                     }
370 
371                     if (logger.isDebugEnabled())
372                     {
373                         logger.debug("Profiler: Processing profile for role " + roleProfile.getRoleName());
374                     }
375                 }
376 
377                 // Create a new profile for the user
378                 ProfileLocator locator = createLocator();
379                 locator.setUser((JetspeedUser) data.getUser());
380                 locator.setMediaType(mediaType);
381                 locator.setName(this.resourceDefault + this.resourceExt);
382 
383                 // Regenerate the portlet ids so they are unique
384                 org.apache.jetspeed.util.PortletUtils.regenerateIds(portlets);
385 
386                 // Save the new profile to permament storage
387                 result = this.createProfile(locator, portlets);
388 
389             }
390             catch (Exception e) 
391             {
392                 logger.error("Exception",  e);
393             }
394         }
395 
396         return result;
397     }
398 
399     /***
400      *  get the Profile object using the Rundata state and capability map
401      *  this is the mapping functionality of the profiler
402      *
403      * @param rundata the rundata object for the current request
404      * @param cm the <code>CapabilityMap</code> of the current requesting device
405      * @return a new Profile object
406      */
407     protected Profile fallbackProfile(RunData data, CapabilityMap cm)
408         throws ProfileException
409     {
410         try
411         {
412             JetspeedRunData rundata = (JetspeedRunData)data;
413             Profile profile = createProfile();
414             JetspeedUser user = rundata.getJetspeedUser();
415 
416             // get the media type from the capability map or rundata
417             profile.setMediaType(getMediaType(rundata, cm));
418 
419             //  Is it a group, role, or user resource?
420             //  It can only be one
421             String param = rundata.getParameters().getString( Profiler.PARAM_GROUP );
422 
423             if (null != param)
424             {
425                 // GROUP Resource
426                 profile.setGroup( JetspeedSecurity.getGroup(param) );
427             }
428             else
429             {
430                 param = rundata.getParameters().getString( Profiler.PARAM_ROLE );
431                 if (null != param)
432                 {
433                     // ROLE Resource
434                     if (user.hasLoggedIn())  // disallow role access for anonymous user
435                     {
436                         profile.setRole( JetspeedSecurity.getRole(param) );
437                     }
438                     else
439                     {
440                         profile.setAnonymous(true);
441                         profile.setUser( user );
442                     }
443                 }
444                 else  // it must be a user resource or anonymous resource
445                 {
446                     // accessing another user's resource
447                     param = rundata.getParameters().getString( Profiler.PARAM_USER );
448                     if (null != param)
449                     {
450 
451                         if (param.equals(JetspeedSecurity.getAnonymousUserName()))
452                         {
453                             profile.setAnonymous(true);
454                         }
455                         if (user.getUserName().equals(param))
456                         {
457                             profile.setUser( user );
458                         }
459                         else
460                         {
461                             profile.setUser( JetspeedSecurity.getUser(param) );
462                         }
463                     }
464                     else
465                     {
466                         profile.setAnonymous(user.getUserName().equals(JetspeedSecurity.getAnonymousUserName()));
467                         profile.setUser( user );
468                     }
469                 }
470             }
471 
472             // get resource name
473             StringBuffer resource = new StringBuffer();
474             param = rundata.getParameters().getString( Profiler.PARAM_PAGE );
475             if (null == param)
476             {
477                // the default resource
478                 resource.append( resourceDefault );
479                 resource.append( resourceExt );
480             }
481             else
482             {   // a specific resource
483                 resource.append( param );
484                 if ( -1 == param.indexOf( PATH_EXTENSION_DELIMITER ) )
485                     resource.append( resourceExt );
486             }
487             profile.setName( resource.toString() );
488 
489             // LANGUAGE
490             getLanguageSettings(profile, rundata);
491 
492             PSMLDocument doc = fallback( profile );
493             if (null != doc)
494             {
495                 profile.setDocument( doc );
496                 return profile;
497             }
498         }
499         catch (Exception e)
500         {
501             logger.error( "Exception in fallbackProfile", e );
502             throw new ProfileException(e.toString());
503         }
504         return null;
505     }
506 
507     /***
508      *  get the Profile object using the Rundata state and capability map
509      *  this is the mapping functionality of the profiler
510      *
511      * @param rundata the rundata object for the current request
512      * @return a new Profile object
513      */
514     public Profile getProfile(RunData rundata)
515         throws ProfileException
516     {
517         CapabilityMap cm = null;
518 
519         if (rundata instanceof JetspeedRunData)
520         {
521             cm = ((JetspeedRunData)rundata).getCapability();
522         }
523         else
524         {
525             cm = CapabilityMapFactory.getCapabilityMap( rundata );
526         }
527 
528         return getProfile(rundata, cm);
529     }
530 
531     /***
532      *  get the Profile object using the Rundata state and specific mimetype
533      *
534      * @deprecated Do not use a profiler method based on MimeType
535      *
536      * @param rundata the rundata object for the current request
537      * @param mt the <code>MimeType</code> of the current requesting device
538      * @return a new Profile object
539      */
540     public Profile getProfile(RunData data, MimeType mt)
541         throws ProfileException
542     {
543         CapabilityMap cm = CapabilityMapFactory.getCapabilityMap(mt.toString());
544         return getProfile(data, cm);
545     }
546 
547     /***
548      *  get the Profile object using a profile locator
549      *
550      * @param rundata The rundata object for the current request.
551      * @param locator The locator containing criteria describing the profile.
552      * @return a new Profile object
553      */
554     public Profile getProfile(ProfileLocator locator)
555         throws ProfileException
556     {
557         PSMLDocument doc =  fallback(locator);
558         Profile profile = createProfile(locator);
559         profile.setDocument(doc);
560         return profile;
561     }
562 
563     /*
564      * Gets the language and country parameters from the request using the Turbine locale detector.
565      *
566      * @param profile The profile object which is modified with the new language settings.
567      * @param rundata The request specific state.
568      */
569     protected void getLanguageSettings( Profile profile, RunData rundata )
570     {
571         String language = rundata.getParameters().getString(Profiler.PARAM_LANGUAGE);
572 
573         if (language != null)
574         {
575             // dont use locale based fall back            
576             profile.setLanguage(language);
577 
578             if(! language.equals("-1"))
579             {
580                 String country = rundata.getParameters().getString(Profiler.PARAM_COUNTRY);
581                 if (country != null)
582                 {
583                     profile.setCountry(country);
584                 }
585             }
586         }
587         else
588         {
589             Locale locale = (Locale)rundata.getUser().getTemp("locale");
590             if (locale == null)
591             {
592                 // Get the locale store it in the user object
593                 CustomLocalizationService locService = (CustomLocalizationService) ServiceUtil.getServiceByName(
594                     LocalizationService.SERVICE_NAME);
595                 locale = locService.getLocale(rundata);
596                 if (locale == null)
597                 {
598                     locale = new Locale(
599                                     TurbineResources.getString("locale.default.language", "en"),
600                                     TurbineResources.getString("locale.default.country", "US"));
601                 }
602                 rundata.getUser().setTemp("locale", locale);
603             }
604 
605             if (useFallbackLanguage)
606             {
607                 profile.setLanguage( locale.getLanguage() );
608             }
609 
610             if (useFallbackCountry)
611             {
612                 profile.setCountry( locale.getCountry() );
613             }
614         }
615     }
616 
617     /*
618      * A basic profiler fallback algorithm that starts from the most specific parameters,
619      * going to the least specific parameters. The PsmlManager implementation is passed
620      * a list of ProfileLocators ordered from most specific  to least specific.
621      *
622      * @param locator The profile locator criteria used to locate a profile.
623      * @param rundata The request specific state.
624      * @return The found psml document, or null if not found.
625      */
626     protected PSMLDocument fallbackList( ProfileLocator original, RunData rundata )
627     {
628         try
629         {
630             List locators = new LinkedList();
631             ProfileLocator locator = (ProfileLocator)original.clone();
632 
633             locators.add( locator.clone() );
634 
635             // remove country
636             if (null != original.getCountry())
637             {
638                 locator.setCountry(null);
639                 locators.add( locator.clone() );
640             }
641 
642             // remove language
643             if (null != original.getLanguage())
644             {
645                 locator.setLanguage(null);
646                 locators.add( locator.clone() );
647             }
648 
649             // fallback mediaType
650             if (null != original.getMediaType())
651             {
652                 locator.setMediaType(null);
653                 locators.add( locator.clone() );
654             }
655 
656             if (null != original.getGroup())
657             {
658                 locator.setGroup(null);
659                 locators.add( locator.clone() );
660             }
661             else if (null != original.getRole())
662             {
663                 locator.setRole(null);
664                 locators.add( locator.clone() );
665             }
666             else if (null != original.getUser())
667             {
668                 locator.setUser(null);
669                 locators.add( locator.clone() );
670             }
671             PSMLDocument doc = PsmlManager.getDocument( locators );
672             return doc;
673 
674         }
675         catch (CloneNotSupportedException e)
676         {
677             logger.error("Profiler: Could not clone profile locator object", e);
678         }
679         return null;
680     }
681 
682     /*
683      * A basic profiler fallback algorithm that starts from the most specific parameters,
684      * going to the least specific parameters. The PsmlManager implementation is passed
685      * a list of ProfileLocators ordered from most specific  to least specific.
686      *
687      * This is alternate fallback algorithm.
688      *
689      * @param locator The profile locator criteria used to locate a profile.
690      *
691      * @return PSMLDocument The located document or null.
692      */
693     protected PSMLDocument fallback(ProfileLocator locator)
694     {
695         if (logger.isDebugEnabled())
696         {
697             logger.debug( "Profiler: fallback called with: " + locator );
698         }
699 
700         PSMLDocument doc = PsmlManager.getDocument( locator );
701         if (null != doc)
702             return doc;
703 
704         // remove country
705         if (null != locator.getCountry() && (! locator.getCountry().equals("-1")))
706         {
707             locator.setCountry(null);
708             doc = PsmlManager.getDocument( locator );
709             if (null != doc)
710                 return doc;
711         }
712 
713         // remove language
714         if (null != locator.getLanguage() && (! locator.getLanguage().equals("-1")))
715         {
716             locator.setLanguage(null);
717             doc = PsmlManager.getDocument( locator );
718             if (null != doc)
719                 return doc;
720         }
721 
722         // fallback mediaType
723         if (useFallbackToRoot)
724         {
725             if (null != locator.getMediaType())
726             {
727                 locator.setMediaType(null);
728                 doc = PsmlManager.getDocument( locator );
729                 if (null != doc)
730                     return doc;
731             }
732         }
733 
734         if (!useRoleFallback)
735         {
736             if (null != locator.getGroup())
737             {
738                 locator.setGroup(null);
739                 doc = PsmlManager.getDocument( locator );
740                 if (null != doc)
741                     return doc;
742             }
743             else if (null != locator.getRole())
744             {
745                 locator.setRole(null);
746                 doc = PsmlManager.getDocument( locator );
747                 if (null != doc)
748                     return doc;
749             }
750             else if (null != locator.getUser())
751             {
752                 locator.setUser(null);
753                 doc = PsmlManager.getDocument( locator );
754                 if (null != doc)
755                     return doc;
756             }
757         }
758         return doc;
759 
760     }
761 
762     /***
763      * Lookup the media type from the CapabilitMap.
764      * First the RunData is checked for an explicit media-type request.
765      *
766      * @param cm The <code>CapabilityMap</code> of the current requesting device.
767      * @param rundata, The <code>RunData</code> turbine request context information.
768      * @return a String, the unique name of the media type.
769      */
770     protected String getMediaType(RunData rundata, CapabilityMap cm)
771     {
772         String paramMediaType;
773         String media = null;
774 
775         if (null != rundata)
776         {
777             paramMediaType = rundata.getParameters().getString( Profiler.PARAM_MEDIA_TYPE );
778             if (null != paramMediaType)
779             {
780                 return paramMediaType;
781             }
782         }
783 
784         if (cm != null)
785         {
786             media = cm.getPreferredMediaType();
787         }
788 
789         return media;
790     }
791 
792     /***
793      * Loads the configuration parameters for this service from the
794      * JetspeedResources.properties file.
795      *
796      * @exception throws a <code>InitializationException</code> if the service
797      * fails to initialize
798      */
799     private void initConfiguration() throws InitializationException
800     {
801         profileClass = ServiceHelper.loadModelClass(this, "profile.impl");
802         locatorClass = ServiceHelper.loadModelClass(this, "locator.impl");
803         
804         // get configuration parameters from Jetspeed Resources
805         ResourceService serviceConf = ((TurbineServices)TurbineServices.getInstance())
806                                                      .getResources(ProfilerService.SERVICE_NAME);
807 
808         resourceDefault = serviceConf.getString( CONFIG_RESOURCE_DEFAULT, DEFAULT_CONFIG_RESOURCE_DEFAULT );
809 
810         resourceExt = serviceConf.getString( CONFIG_RESOURCE_EXT, DEFAULT_CONFIG_RESOURCE_EXT );
811         if (-1 == resourceExt.indexOf(PATH_EXTENSION_DELIMITER))
812         {
813             resourceExt = PATH_EXTENSION_DELIMITER + resourceExt;
814         }
815         
816         useSecurity = serviceConf.getBoolean( CONFIG_SECURITY, DEFAULT_CONFIG_SECURITY );
817 
818         useRoleFallback = serviceConf.getBoolean( CONFIG_ROLE_FALLBACK, DEFAULT_CONFIG_ROLE_FALLBACK );
819 
820         newUserTemplate = serviceConf.getString( CONFIG_NEWUSER_TEMPLATE, DEFAULT_CONFIG_NEWUSER_TEMPLATE );
821 
822         useFallbackToRoot = serviceConf.getBoolean( CONFIG_FALLBACK_TO_ROOT, useFallbackToRoot );
823 
824         useFallbackLanguage = serviceConf.getBoolean( CONFIG_FALLBACK_LANGUAGE, useFallbackLanguage );
825 
826         useRoleMerge = serviceConf.getBoolean( CONFIG_ROLE_MERGE, useRoleMerge );
827 
828         rolemergeControl = serviceConf.getString( CONFIG_ROLE_MERGE_CONTROL, DEFAULT_CONFIG_ROLE_MERGE_CONTROL );
829 
830         rolemergeController = serviceConf.getString( CONFIG_ROLE_MERGE_CONTROLLER, DEFAULT_CONFIG_ROLE_MERGE_CONTROLLER );
831 
832         if (useFallbackLanguage == false)
833         {
834             useFallbackCountry = false;
835         }
836         else
837         {
838             useFallbackCountry = serviceConf.getBoolean( CONFIG_FALLBACK_COUNTRY, useFallbackCountry );
839         }
840 
841         try
842         {
843             mediaTypes = serviceConf.getStringArray(CONFIG_NEWUSER_MEDIA);
844         }
845         catch (Exception e)
846         {
847             logger.error( "Error getting media types", e );
848         }
849 
850         if (null == mediaTypes || mediaTypes.length == 0)
851         {
852             mediaTypes = DEFAULT_CONFIG_NEWUSER_MEDIA;
853         }
854     }
855 
856    /***
857     * Builds a dynamic URI based on the current profiler group/role/page
858     *
859     * @param data The rundata object for the current request.
860     * @param locator The description of the profile.
861     * @return A new dynamic URI representing all profile parameters from the locator.
862     */
863     public DynamicURI makeDynamicURI( RunData data, ProfileLocator locator )
864         throws ProfileException
865     {
866         DynamicURI uri = new DynamicURI( data );
867 
868        // check mediatype to add to the uri
869         String mtype = locator.getMediaType();
870         if (null != mtype)
871         {
872             uri.addPathInfo(Profiler.PARAM_MEDIA_TYPE, mtype);
873         }
874 
875        // check language to add to the uri
876         String language = locator.getLanguage();
877         if (null != language)
878         {
879             uri.addPathInfo(Profiler.PARAM_LANGUAGE, language);
880         }
881 
882        // check language to add to the uri
883         String country = locator.getCountry();
884         if (null != country)
885         {
886             uri.addPathInfo(Profiler.PARAM_COUNTRY, country);
887         }
888 
889         // check User, Group or Role to add to the uri
890         JetspeedUser user = locator.getUser();
891         if (null != user)
892         {
893             if (user.getUserName() != null)
894                 uri.addPathInfo(Profiler.PARAM_USER, user.getUserName());
895         }
896         else
897         {
898             Group group = locator.getGroup();
899             if (null != group)
900             {
901                 uri.addPathInfo(Profiler.PARAM_GROUP, group.getName());
902             }
903             else
904             {
905                 Role role = locator.getRole();
906                 if (null != role)
907                 {
908                     uri.addPathInfo(Profiler.PARAM_ROLE, role.getName());
909                 }
910             }
911         }
912 
913         // check Page to add to the uri
914         String page = locator.getName();
915         if (null != page)
916         {
917             uri.addPathInfo(Profiler.PARAM_PAGE, page);
918         }
919 
920         return uri;
921     }
922 
923     /***
924      * Creates a new Profile object that can be successfully managed by
925      * the current Profiler implementation
926      *
927      * @return A new Profile object
928      */
929     public Profile createProfile()
930     {
931         return (Profile)ServiceHelper.createObject(this.profileClass);
932     }
933 
934     /***
935      * Creates a new Profile object for a specific locator.
936      *
937      * @param locator The description of the profile.
938      * @return A new Profile object
939      */
940     public Profile createProfile(ProfileLocator locator)
941     {
942         Profile profile = (Profile)ServiceHelper.createObject(this.profileClass);
943         profile.init(locator);
944         return profile;
945     }
946 
947     /***
948      * Creates a new ProfileLocator object that can be successfully managed by
949      * the current Profiler implementation
950      *
951      * @return A new ProfileLocator object
952      */
953     public ProfileLocator createLocator()
954     {
955         return (ProfileLocator)ServiceHelper.createObject(this.locatorClass);
956     }
957 
958     /***
959      * Create a new profile given a profile locator
960      * 
961      * This method assumes that you have cloned and regenerated the
962      * portlet ids if the portlets come from another profile.
963      *
964      * @param locator The description of the new profile to be created.
965      * @param portlets The PSML tree
966      */
967 
968     public Profile createProfile(ProfileLocator locator, Portlets portlets)
969             throws ProfileException
970     {
971         if (portlets == null)
972         {
973             portlets = new PsmlPortlets();
974         }
975         
976         Profile profile = createProfile(locator);
977         PSMLDocument doc = new BasePSMLDocument(null, portlets);
978         profile.setDocument(doc);
979         doc = PsmlManager.createDocument(profile);
980         profile.setDocument(doc);
981         return profile;
982     }
983 
984     /***
985      * Create a new profile.
986      * The profile parameter's document will be cloned.
987      *
988      * @param rundata The rundata object for the current request.
989      * @param profile The description of the new profile to be created.
990      * @param contentType create a profile for the specific contentType
991      * @param from create a profile by cloning the profile from the specific user (if null - turbine is used)
992      * @return The newly created profile.
993      * -----------------------------------------------------------
994      * Andreas Kempf, Siemens ICM S CP PE, Munich
995      */
996 
997     /***
998      * This methode creates a wml profile and a html profile
999      * for a new user
1000      */
1001 
1002     public Profile createProfile( RunData data, Profile profile, String contentType, String from )
1003             throws ProfileException
1004     {
1005       if ((contentType == null) || (contentType.length() < 2))
1006         contentType = "html";
1007 
1008       if ((from == null) || (from.length() < 2))
1009         from = "turbine";
1010 
1011 
1012         if ((null == profile.getDocument()) || (!profile.getMediaType().equalsIgnoreCase (contentType)))
1013         {
1014             // locate the default resource
1015 
1016             // TODO: make this configurable
1017 
1018             try
1019             {
1020                 ProfileLocator locator = createLocator();
1021                 locator.setUser( JetspeedSecurity.getUser(from) );
1022 
1023                 locator.setMediaType(contentType);
1024                 PSMLDocument doc = fallback(locator);
1025 
1026                 if (doc != null)
1027                 {
1028                     PSMLDocument clonedDoc = (PSMLDocument) SerializationUtils.clone(doc);
1029                     org.apache.jetspeed.util.PortletUtils.regenerateIds(clonedDoc.getPortlets());
1030                     profile.setDocument(clonedDoc);
1031                 }
1032 
1033                 profile.setName( resourceDefault + resourceExt );
1034 
1035             }
1036             catch (Exception e)
1037             {
1038                 logger.error( "Error creating profile", e );
1039                 throw new ProfileException(e.toString());
1040             }
1041         }
1042 
1043         try
1044         {
1045             profile.setMediaType(contentType);
1046 
1047             PSMLDocument doc = PsmlManager.createDocument(profile);
1048             Profile newProfile = (Profile)profile.clone();
1049             newProfile.setDocument(doc);
1050 
1051             return newProfile;
1052         }
1053         catch (CloneNotSupportedException e)
1054         {
1055             logger.error("Could not clone profile locator object: ", e);
1056         }
1057         return null;
1058 
1059     }
1060 
1061    /*** Create a new profile.
1062      *
1063      * @deprecated Should be removed when old customizer is removed.
1064      *
1065      * @param rundata The rundata object for the current request.
1066      * @param profile The description of the new profile to be created.
1067      * @param mt The specific mime type, which is converted to a mediatype.
1068      * @return The newly created profile.
1069      */
1070     public Profile createProfile( RunData data, Profile profile, MimeType mt )
1071         throws ProfileException
1072     {
1073         CapabilityMap cm = CapabilityMapFactory.getCapabilityMap(mt.getContentType());
1074         profile.setMediaType( getMediaType(data, cm) );
1075         return createProfile(data, profile);
1076     }
1077 
1078    /***
1079      *  Removes a profile.
1080      *
1081      * @param locator The profile locator criteria.
1082      */
1083     public void removeProfile( ProfileLocator locator )
1084     {
1085         PsmlManager.removeDocument(locator);
1086     }
1087 
1088     /*** Query for a collection of profiles given a profile locator criteria.
1089      *
1090      * @param locator The profile locator criteria.
1091      * @return The list of profiles matching the locator criteria.
1092      */
1093     public Iterator query( QueryLocator locator )
1094     {
1095         return PsmlManager.query( locator );
1096     }
1097 
1098     /***
1099      * @see org.apache.jetspeed.services.profiler.ProfilerService#useRoleProfileMerging
1100      */
1101     public boolean useRoleProfileMerging()
1102     {
1103         return this.useRoleFallback && this.useRoleMerge;
1104     }
1105 }
1106