Archive

Archive for 30 enero 2012

Filtrando colecciones con functores y lambdas


La librería estándar de C++ nos provee varias clases que nos permiten almacenar datos. Por ejemplo, std::list para listas, std::map para diccionarios, std::vector para elementos contíguos, etcétera. Todas estas clases de colecciones, también llamadas contenedores, están diseñadas para que su contenido pueda ser accedido mediante clases que enumeran cada elemento. A éstas se les llama iteradores. Los métodos begin y end nos proveen el primer y último iterador. Así, cualquier contenedor puede recorrerse de manera similar al siguiente ejemplo:

list<int> nums;
...

for (list<int>::iterator i = nums.begin(); i != nums.end(); ++i)
{
    cout << *i << endl;
}

O también podemos usar la nueva palabra reservada "auto":

list<int> nums;
...

for (auto i = nums.begin(); i != nums.end(); ++i)
{
    cout << *i << endl;
}

Por otra parte, recorrer un contenedor suele ser tan común que la librería estándar nos propone una función que toma tres parámetros: el iterador de inicio, el iterador de fin, y como tercer parámetro “algo” que tome un parámetro y ejecute una acción. Digo “algo” porque dado que es una plantilla, puede ser una función o, mejor aún, un functor. Un functor, antes que preguntes, es una clase que sobreescribe el operador () de tal suerte que puede invocarse como si fuera una función.

Para ejemplificar estos conceptos, me gustaría antes proponer una clase. Supongamos que vamos a trabajar con empleados. Creamos una clase similar a ésta.

class employee
{
    public:
        enum sex { sexMale, sexFemale };

        employee()
        {
            _age = 0;
            _gender = sexMale;
        }

        employee(const employee& copy)
        {
            _name = copy._name;
            _age = copy._age;
            _gender = copy._gender;
        }

        employee(wstring name, sex gender, int age)
        {
            _name = name;
            _age = age;
            _gender = gender;
        }

        wstring _name;
        sex _gender;
        int _age;
        
        bool operator== (const employee& employee)
        {
            return _name == employee._name;
        }

        bool operator< (const employee& employee)
        {
            return _name < employee._name;
        }
};

Ahora, en nuestra función main, supongamos que creamos un contenedor de esta forma.

employee emp1(L"Fernando", employee::sexMale, 28);
employee emp2(L"Catalina", employee::sexFemale, 31);
employee emp3(L"Moisés", employee::sexMale, 31);
employee emp4(L"Emmanuel", employee::sexMale, 27);
employee emp5(L"Paula", employee::sexFemale, 18);
employee emp6(L"Pedro", employee::sexMale, 24);

list<employee> employees;
employees.push_back(emp1);
employees.push_back(emp2);
employees.push_back(emp3);
employees.push_back(emp4);
employees.push_back(emp5);
employees.push_back(emp6);

Bueno bueno. Luego entonces, decíamos que podemos usar functores. Por ejemplo, creemos uno para imprimir todo el contenido de la lista.

class print_employee
{
    public:
        print_employee()
        {
            _print_all = false;
        }

        print_employee(bool print_all)
        {
            _print_all = print_all;
        }

        void operator()(const employee& employee)
        {
            wcout << employee._name << endl;
            if (_print_all)
            {
                wcout << L"Gender: "  
                    << (employee._gender == employee::sexMale ? L"Male" : L"Female") 
                    << endl;
                wcout << L"Age: " << employee._age << endl;
                wcout << L"*****\n" << endl;
            }
        }

        bool _print_all;
};

 

Ahora sí, usar la función for_each de esta forma.

print_employee print(false);
for_each(employees.begin(), employees.end(), print);

Como puedes ver, print_employee es una clase functor: ve cómo sobrecargamos el operador (). De hecho, podrías invocar directamente print(empleado), como si fuera una función; eso es justamente lo que hace for_each.

Ahora bien, una de las tareas que frecuentemente tendremos que hacer es filtrar un contenedor. Por ejemplo, podríamos querer filtrar los empleados por género, por edad o por nombre. Pues bien, lo que haremos será crear una clase con un functor. Esta clase tendrá un constructor que tomará como parámetro una referencia hacia el contenedor donde guardaremos los empleados que cumplan la condición del filtro. Veamos.

class filter_employee
{
    public:
        filter_employee(list& results)
            : _results(results)
        {
            _by_age = false;
            _min_age = 0;
            _max_age = 0;
            _by_name = false;
        }

        void by_age(int min, int max)
        {
            _by_age = true;
            _min_age = min;
            _max_age = max;
        }

        void by_name(const wstring& name)
        {
            _by_name = true;
            _name = name;
        }

        void operator()(const employee& value)
        {
            bool found = false;
            if (_by_age && !found)
            {
                found = value._age >= _min_age && 
                        value._age <= _max_age;
            }

            if (_by_name && !found)
            {
                found = value._name.find(_name) != wstring::npos;
            }

            if (found)
                _results.push_back(value);
        }

        list& _results;

    private:
        bool _by_age;
        int _min_age;
        int _max_age;
        bool _by_name;
        wstring _name;
};

Si invocamos by_age entonces el functor filtrará por un rango de edades. Si invocamos by_name, entonces buscará una subcadena de texto. Ahora, podemos usar la clase así, para filtrar empleados entre 28 y 42 años:

list results;

filter_employee filter(results);
filter.by_age(28, 42);
for_each(employees.begin(), employees.end(), filter);
for_each(results.begin(), results.end(), print);

Del código anterior, la primera línea inicializa una lista de empleados, donde el functor guardará los resultados obtenidos. La tercera línea inicializa el functor y la cuarta establece que se filtre por edad entre 28 y 42. La quinta ejecuta el filtrado: el functor será llamado para cada elemento y sólo los que cumplan la condición se almacenarán en la lista results. Por último, invocamos el functor de impresión que creamos anteriormente y pasamos los iteradores de results.

Y así, Charlie Brown, es como podemos usar functores para realizar filtros sobre colecciones. La verdad es que for_each nos ayuda bastante, aunque es cierto que crear una clase functor puede resultar tedioso. En los ejemplos anteriores, los functores son clases un poco más complicadas ya que permiten realizar ciertas configuraciones (al menos con filter_employee). Habrá casos, por supuesto, en los que no valga la pena crear un functor. Por ejemplo, print_employee sale sobrando. Para estas situaciones podemos usar lambdas.

Los lambdas son funciones anónimas, introducidas en C++ a partir del estándar C++11 (es decir, el del 2011). Aunque son recientes, afortunadamente Visual C++ 2010 ya las implementa, por lo que podemos hacer uso de ellas (obvio, si usas VC++; aunque supongo que otros compiladores de C++ ya habrán implementado los lambdas).

El siguiente código muestra un lambda para imprimir el empleado, así como su uso junto con for_each.

auto print_func = [](const employee& value) { wcout << value._name << endl; };

for_each(employees.begin(), employees.end(), print_func);

Como puedes ver, print_func es una función lambda. Los corchetes [] así lo indican, seguido de los parámetros (en este caso, una referencia constante a un employee), seguido del cuerpo de la función: en este caso, la impresión hacia la consola del nombre. Por supuesto, los lambdas pueden ser más complejos: podemos crear una también para filtrar por nombre, como en el siguiente código.

auto filter_func = [&results](const employee& value) { 
    if (value._name.find(L"P") != wstring::npos)
        results.push_back(value);
};
auto print_func = [](const employee& value) { wcout << value._name << endl; };
for_each(employees.begin(), employees.end(), filter_func);
for_each(results.begin(), results.end(), print_func);

En la primera línea del código anterior declaramos nuestro lambda. Nota que en esta ocasión entre los corchetes hemos colocado “&results”. Recordamos que results es la lista donde almacenamos los resultados. Así, esa expresión significa: pasa una referencia hacia results como parámetro a la función lambda. Si no ponemos nada entre corchetes, no podemos hacer referencia hacia results. Si dejamos el ámperson nada más (i.e. [&](const employee& value)) entonces indicamos que todas las variables que se declaren fuera de la lambda sean asequibles por referencia. Si colocamos el nombre results sin el ámperson, entonces quiere decir que lo pasamos por valor.

Así, como puedes ver, podemos usar cualquiera de los dos enfoques para realizar filtrado sobre un contenedor. Por supuesto que hay otros, pero estos son los que mejor me han funcionado. Mi recomendación es que juegues un poco con el código anterior y practiques.

¡Suerte!

Categorías:Apunte, C++ Etiquetas: , ,

Cómo guardar contenido cuando el usuario no tiene permisos en una lista de SharePoint


Cómo guardar contenido cuando el usuario no tiene permisos en una lista

He observado con mi equipo de trabajo, colegas y sharepointeros en general que una de los problemas que tenemos cuando comenzamos a usar el modelo de objetos de SharePoint es el tema de la seguridad. De hecho a mí mismo me pasaba que tenía que crear un WebPart, el cual obtendría datos de una lista o biblioteca, los renderiza, y al final presenta alguna acción que causa una actualización en dicha fuente. Por supuesto, en ambiente de desarrollo todo jala de pelos, porque usamos la cuenta del administrador. Pero tan pronto publicamos para que el cliente pruebe, ¡boom! Un SecurityException es nuestra recompensa.

Por supuesto, el problema en estos casos suele ser que el usuario que ha ingresado no tiene permisos para acceder a una biblioteca. El caso suele ser sencillo: darle permisos a dicho usuario, pues si no los tiene, su razón ha de tener. En otras palabras, si el usuario no tiene permiso es por la lógica de seguridad implementada para la lista o biblioteca en cuestión.

Sin embargo hay ocasiones en las que el cliente quiere compartir sólo cierta información. Por ejemplo, imagina una lista donde se guarda información sobre los proveedores de una empresa y la tarifa que se les paga a cada uno por unidad de servicio. Por otro lado, el jefe de compras decide que quiere exponer una forma para que los usuarios de otras áreas busquen información de los proveedores, pero no quiere que la tarifa quede expuesta. Además, si el proveedor no buscado no existe, el usuario debería poder darlo de alta (nuevamente, sin capturar la tarifa). ¿Qué podemos hacer?

No podemos simplemente dar permisos de lectura a los usuarios generales porque entonces podrían visualizar la página AllItems.aspx de la lista y les mostraría la tarifa. Luego, lo que tenemos que hacer es un WebPart que consulte la lista y haga la búsqueda ahí, aunque el usuario autenticado no tenga permisos de lectura en la misma. Y lo mismo ocurre con el alta de un proveedor: el usuario deberá ingresar la información sin tener permisos para hacerlo.

Afortunadamente, el modelo de objetos de SharePoint nos proporciona una forma de hacer el “by-pass” de seguridad. La clase SPSecurity (ubicada en el espacio de nombres Microsoft.SharePoint) contiene un método llamado RunWithElevatedPrivileges. Éste toma un sólo parámetro: un delegado de un método sin parámetros que regresa void.

protected override void CreateChildControls()
{ 
    SPGridView myView = new SPGridView();
    ...

    SPSecurity.RunWithElevatedPrvileges( () => {

        // aunque el usuario autenticado no tenga
        // permisos para leer o modificar la lista "Proveedores",
        // al ejecutarse este delegado anónimo sí que
        // los tiene. 
        SPList list = SPContext.Current.Lists["Proveedores"];
        myView.DataSource = list.Items;
        myView.DataBind();
    });

    Controls.Add(myView);
}

El código anterior muestra la forma básica de utilizar dicho método. Como ves, le pasamos un método anónimo. Si el usuario que carga el WebPart no tiene permisos para ingresar, éste código le garantizará el pase. Lo mismo para actualizar datos.

protected void OnOK(object sender, EventArgs args)
{
    SPSecurity.RunWithElevatedPrivileges(() => {

        SPList list = SPContext.Current.Lists["Proveedor"];
        SPListItem item = list.Items[0];
        item["Título"] = "Nuevo título";
        item.Update();
    });
}

Ahora bien, hay que aclarar un asunto. SPSecurity.RunWithElevatedPrivileges no es que de un paso libre así tal cual. Lo que hace en realidad es darle los privilegios del usuario que corre el proceso. Así, si corremos el código anterior dentro de un WebPart, éste se ejecutará bajo los permisos que tenga la cuenta asociada al Application Pool (y usualmente será el administrador de la granja). Pero por otro lado, si corriésemos un código similar en, digamos, un temporizador de SharePoint o un Workflow, la cuenta que tendrá será la asociada a estos procesos (digamos, al servicio SPTimerv4).

Sin embargo, por lo anterior, hay que tener cuidado ya que no siempre funcionará. Si por ejemplo, ejecutamos desde un temporizador y la cuenta asociada a ésta no tiene permiso en el recurso que queremos consultar (i.e. la lista “Proveedor” en nuestro ejemplo) tendremos un SPException como si no tuviésemos permiso. Lo mismo ocurre si ejecutamos dicho código desde una aplicación (de consola o Windows Forms): el usuario que ejecuta la aplicación será quien deba tener permisos en la lista.

Así que ya sabes, no siempre será garantía su empleo. Sin embargo, para WebParts, páginas de aplicación y controles, siempre será una muy buena opción.

Leyendo columnas de búsqueda (Lookup) en SharePoint


Buen inicio de año a todos, feliz 2012, feliz nuevo b’ak’tun… una disculpa por mi silencio de noviembre y diciembre, pero bueno, ya saben: miles de proyectos, todo urge… yada yada yada. Pero ya vine con un post pequeño pero que es útil en tu programación para SharePoint run-of-the-mill.

Una característica padre que tiene SharePoint es que te permite crear campos de búsqueda en una lista cualquiera. Estos campos hacen referencia a campos dentro de otra lista o biblioteca, de tal suerte que permiten crear una relación padre/hijo.

Veamos un ejemplo jocoso. Supongamos que estamos creando un SharePoint para el colegio inglés Hogwarts. Pensemos que estamos armando un catálogo que contiene información sobre los cursos que daría el famoso auror Alastor Moody. Cada curso tiene asociados diversos temas, y cada tema tiene asociadas las preguntas del examen que el profesor practicará a los estudiantes.

image

Creamos nuestra lista de preguntas: una lista personalizada sencilla. Por ejemplo:

image

Posteriormente, creamos nuestra lista de temas. Pero cada tema debe tener asociada una o más preguntas, por lo que podemos sacar provecho el campo búsqueda: añadimos uno, le decimos que busque en la lista “Preguntas” y que permita valores múltiples. La siguiente imagen muestra cómo luce la lista cuando estamos creando un nuevo tema, y cómo se ve la vista de tabla.

image

image

Y algo muy similar para los cursos: creamos un campo de búsqueda y lo asociamos a los temas.

image

Ahora bien, como administradores eso está padre, y todo, pero nada nuevo bajo el sol. Lo interesante ocurre cuando tenemos que crear algún control en el cual tengamos que leer dicha información.

Afortunadamente SharePoint nos provee con una clase que nos hace la vida más fácil: SPFieldLookupValueCollection. Las columnas de búsqueda con opción múltiple (como la de Temas en la imagen anterior) pueden convertirse en esta clase, y ahí podemos iterar para obtener los objetos SPFieldLookupValue, los cuales cuentan con dos propiedades importantes: LookupId y LookupValue. De esta forma podemos buscar relaciones fácilmente.

SPWeb web = SPContext.Current.Web;

SPList cursos = web.List["Cursos"];
SPList temas = web.List["Temas"];

foreach (SPListItem curso in cursos.Items)
{
    Console.WriteLine("Curso: {0}", curso.DisplayName);

    var temasEnCursos = curso["Temas"] as SPFieldLookupValueCollection;
    foreach (var temaLookup in temasEnCursos)
    {
        var tema = temas.Items.GetItemById(temaLookup.LookupId);
        Console.WriteLine("Tema: {0}", tema.DisplayName);
    }
    Console.WriteLine("=====\n");
}

Dado que SPFieldLookupValueCollection hereda de List<SPFieldLookupValue>, tenemos disponible toda la funcionalidad de List<T>. Por ejemplo, podemos usar el método Find para obtener todos los elementos que comienzan con A:

SPWeb web = SPContext.Current.Web;

SPList cursos = web.List["Cursos"];
SPList temas = web.List["Temas"];

foreach (SPListItem curso in cursos.Items)
{
    Console.WriteLine("Curso: {0}", curso.DisplayName);

    var temasEnCursos = curso["Temas"] as SPFieldLookupValueCollection;
    var cursosFiltrados = temasEnCursos.Find(x => x.StartsWith("A"));

    foreach (var temaLookup in cursosFiltrados)
    {
        Console.WriteLine("Tema: {0}", temaLookup.LookupValue);
    }
}

Por otra parte, SPFieldLookupValueCollection tiene el método ToString sobrecargado. Al invocarlo, nos regresa una cadena de texto con el formato: ID;#Texto;ID;#Texto. Por ejemplo:

var temasEnCursos = curso["Temas"] as SPFieldLookupValueCollection;
Console.WriteLine(temasEnCursos.ToString());

Nos imprime algo como:

2;#Cruciatus;#3;#Imperio;#1;#Avada Kedavra

 

Si quisiéramos crear un nuevo objeto, podemos usar el constructor que toma un parámetro: una cadena con el formato anterior. Por ejemplo:

var texto = "2;#Cruciatus;#3;#Imperio;#1;#Avada Kedavra";
var nuevoTema = new SPFieldLookupValueCollection(texto);
curso["Temas"] = nuevoTema;
curso.Update();

Como podemos ver, es muy sencillo utilizar este tipo de campos en nuestros desarrollos para SharePoint. Y esto gana en la vida, porque usar este tipo de columnas nos permite establecer relaciones maestro-hijo de forma muy sencilla. La alternativa sería usar una tercera lista que enlace ambas entidades. Pero eso es muy de base de datos, y no tanto de SharePoint.

Tschüss!