/*
 * Copyright 2013 Harald Wellmann
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ops4j.pax.shiro.faces.tags;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Modifier;

import javax.faces.component.FacesComponent;

import org.apache.shiro.ShiroException;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * Tag used to print out the String value of a user's default principal, or a specific principal as
 * specified by the tag's attributes.
 * <p/>
 * If no attributes are specified, the tag prints out the {@code toString()} value of the user's
 * default principal. If the {@code type} attribute is specified, the tag looks for a principal with
 * the given type. If the {@code property} attribute is specified, the tag prints the string value
 * of the specified property of the principal. If no principal is found or the user is not
 * authenticated, the tag displays nothing unless a {@code defaultValue} is specified.
 */
@FacesComponent("org.ops4j.pax.shiro.component.Principal")
public class PrincipalComponent extends ShiroOutputComponent {

    /**
     * The type of principal to be retrieved, or null if the default principal should be used.
     */
    private String type;

    /**
     * The property name to retrieve of the principal, or null if the <tt>toString()</tt> value
     * should be used.
     */
    private String property;

    /**
     * The default value that should be displayed if the user is not authenticated, or no principal
     * is found.
     */
    private String defaultValue;
    
    enum PropertyKeys {
        type,
        property,
        defaultValue;
    }

    public String getType() {
        return (String) getStateHelper().eval(PropertyKeys.type);
    }

    public void setType(String type) {
        getStateHelper().put(PropertyKeys.type, type);
    }

    public String getProperty() {
        return (String) getStateHelper().eval(PropertyKeys.property);
    }

    public void setProperty(String property) {
        getStateHelper().put(PropertyKeys.property, property);
    }

    public String getDefaultValue() {
        return (String) getStateHelper().eval(PropertyKeys.defaultValue);
    }

    public void setDefaultValue(String defaultValue) {
        getStateHelper().put(PropertyKeys.defaultValue, defaultValue);
    }
    
    @Override
    public Object getValue() {
        String strValue = null;

        if (getSubject() != null) {
            // Get the principal to print out
            Object principal;

            if (type == null) {
                principal = getSubject().getPrincipal();
            }
            else {
                principal = getPrincipalFromClassName();
            }

            // Get the string value of the principal
            if (principal != null) {
                if (property == null) {
                    strValue = principal.toString();
                }
                else {
                    strValue = getPrincipalProperty(principal, property);
                }
            }
        }

        if (strValue == null) {
            strValue = defaultValue;
        }
        return strValue;
    }
    
    private Object getPrincipalFromClassName() {
        Object principal = null;

        try {
            Class<?> cls = Class.forName(type);
            PrincipalCollection principals = getSubject().getPrincipals();
            if (principals != null) {
                principal = principals.oneByType(cls);
            }
        }
        catch (ClassNotFoundException e) {
            log.error("Unable to find class for name [" + type + "]");
        }
        return principal;
    }

    private String getPrincipalProperty(Object principal, String _property) {
        String strValue = null;
        boolean foundProperty = false;
        try {
            BeanInfo bi = Introspector.getBeanInfo(principal.getClass());

            // Loop through the properties to get the string value of the specified property
            for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
                if (pd.getName().equals(_property)
                    && (Modifier.isPublic(pd.getReadMethod().getModifiers()))) {
                    Object value = null;
                    try {
                        pd.getReadMethod().setAccessible(true);
                        value = pd.getReadMethod().invoke(principal, (Object[]) null);
                    }
                    finally {
                        pd.getReadMethod().setAccessible(false);
                    }
                    strValue = String.valueOf(value);
                    foundProperty = true;
                    break;
                }
            }

        }
        catch (ReflectiveOperationException exc) {
            throw new ShiroException(exc);
        }
        catch (IntrospectionException exc) {
            throw new ShiroException(exc);
        }
        if (!foundProperty) {
            final String message = "Property [" + _property + "] not found in principal of type ["
                + principal.getClass().getName() + "]";
            log.error(message);
            throw new ShiroException(message);
        }
        return strValue;
    }
}

@@ -1704,28 +1704,61 @@
 nent %7B%0A%0A    
-/**%0A
+enum PropertyKeys %7B%0A%0A        /**%0A    
      * The t
@@ -1849,45 +1849,42 @@
     
+    
 */%0A    
-private String
+   
  type
-;
+,
 %0A%0A
+    
     /**%0A
     
@@ -1871,32 +1871,36 @@
 e,%0A%0A        /**%0A
+    
      * The prope
@@ -1957,12 +1957,15 @@
 the 
-%3Ctt%3E
+%7B@code 
 toSt
@@ -1974,13 +1974,9 @@
 ng()
-%3C/tt%3E
+%7D
  val
@@ -1983,16 +1983,20 @@
 ue%0A     
+    
 * should
@@ -2010,37 +2010,30 @@
 d.%0A     
+    
 */%0A    
-private String
+   
  propert
@@ -2037,19 +2037,27 @@
 erty
-;
+,
 %0A%0A    
+ 
+   
 /**%0A
+    
     
@@ -2157,16 +2157,20 @@
 al%0A     
+    
 * is fou
@@ -2182,104 +2182,14 @@
     
-*/%0A
     
-private String defaultValue;%0A    %0A    enum PropertyKeys %7B%0A        type,%0A        property,
+*/
 %0A   
@@ -3109,20 +3109,25 @@
     if (
-t
+getT
 ype
+()
  == null
@@ -3402,24 +3402,29 @@
     if (
-p
+getP
 roperty
+()
  == null
@@ -3584,24 +3584,29 @@
 ncipal, 
-p
+getP
 roperty
+()
 );%0A     
@@ -3698,17 +3698,20 @@
 Value = 
-d
+getD
 efaultVa
@@ -3709,24 +3709,26 @@
 DefaultValue
+()
 ;%0A        %7D%0A
@@ -3901,20 +3901,25 @@
 forName(
-t
+getT
 ype
+()
 );%0A     
@@ -4206,20 +4206,25 @@
 me %5B%22 + 
-t
+getT
 ype
+()
  + %22%5D%22);

read properties via state helper