Index: contrib/funambol/trunk/modules/psync/build/build.properties
===================================================================
--- contrib/funambol/trunk/modules/psync/build/build.properties	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/build/build.properties	(revision 2082)
@@ -0,0 +1,9 @@
+# Properties related to the user's environment.
+
+# version dependant jar files
+common-pim.jar=funambol-common-pim-6.0.3.jar
+
+psync.name=funambol-psync
+psync.release.major=1
+psync.release.minor=0
+psync.build.number=0
Index: contrib/funambol/trunk/modules/psync/build/build.xml
===================================================================
--- contrib/funambol/trunk/modules/psync/build/build.xml	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/build/build.xml	(revision 2082)
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  =============================================================================
+    Build file for the pSync Connector.
+
+    The following properties need to be set in a ./build.properties,
+    ${user.home}/build.properties, a higher level build file, on the command
+    line, ... :
+
+    This script should be started with the following command line :
+
+        ant <target>
+
+    Run "ant usage" to get a list of available targets. The default target is
+    "deploy"
+  =============================================================================
+-->
+
+	<project basedir=".." default="pack" name="pSync Connector">
+
+    <!-- Pick up the environment variables -->
+    <property environment="ENV"/>
+
+    <!-- Pick up user project properties -->
+    <property file="${user.home}/psync-build.properties"/>
+
+    <!-- Pick up default project properties -->
+    <property file="build/build.properties"/>
+
+    <!-- =============================================== -->
+    <!-- Definitions                                     -->
+    <!-- =============================================== -->
+    <property name="dir.lib"            value="lib"/>
+    <property name="dir.src"            value="src"/>
+    <property name="dir.src.java"       value="src/main/java"/>
+    <property name="dir.src.sql"        value="src/main/sql"/>
+    <property name="dir.src.bean"       value="src/main/bean"/>
+    <property name="dir.output"         value="output"/>
+    <property name="dir.output.javadoc" value="output/javadoc"/>
+    <property name="dir.output.html"    value="output/html"/>
+    <property name="dir.output.classes" value="output/classes"/>
+    <property name="dir.output.javacc"  value="output/javacc"/>
+
+    <!-- =================================================================== -->
+    <!-- =================================================================== -->
+
+
+    <!-- =============================================== -->
+    <!-- USAGE                                           -->
+    <!-- =============================================== -->
+    <target name="usage">
+
+        <echo message=""/>
+        <echo message="pSync build file"/>
+        <echo message="------------------------------------------------------"/>
+        <echo message=""/>
+        <echo message=" Available targets are :"/>
+        <echo message=""/>
+        <echo message=" usage       --> help on usage"/>
+        <echo message=" build       --> builds the project"/>
+        <echo message=" pack        --> generates binary files"/>
+        <echo message=" clean       --> cleans up the build directory"/>
+        <echo message=" docs        --> generates javadocs"/>
+        <echo message=" env         --> displays the current environment"/>
+        <echo message=""/>
+
+    </target>
+
+    <!-- =============================================== -->
+    <!-- ENV                                             -->
+    <!-- =============================================== -->
+
+    <target name="env">
+         <echoproperties/>
+    </target>
+
+
+    <!-- =================================================================== -->
+    <!-- =================================================================== -->
+
+    <!-- =============================================== -->
+    <!-- INIT                                            -->
+    <!-- =============================================== -->
+    <target name="init">
+        <property name="psync.version" value="${psync.release.major}.${psync.release.minor}.${psync.build.number}"/>
+
+        <!-- Directory set up -->
+        <mkdir dir="${dir.output.classes}"/>
+
+    </target>
+
+
+    <!-- =============================================== -->
+    <!-- BUILD                                           -->
+    <!-- =============================================== -->
+    <target name="build" depends="init">
+        <javac debug="on" deprecation="true"
+               destdir="${dir.output.classes}"
+               includeAntRuntime="no"
+               includes="**/*java"
+               source="1.5"
+               srcdir="${dir.src.java}">
+            <classpath>
+                <fileset dir="lib">
+                <include name="**/*.jar"/>
+                </fileset>
+            </classpath>
+        </javac>
+    </target>
+
+    <!-- =============================================== -->
+    <!-- PACK                                            -->
+    <!-- =============================================== -->
+    <target name="pack" depends="build">
+        <property name="dir.module" value="${dir.output}/${psync.name}"/>
+
+        <!--
+            Create the package directory structure
+        -->
+        <mkdir dir="${dir.module}/config"/>
+        <mkdir dir="${dir.module}/lib"/>
+        <mkdir dir="${dir.module}/sql"/>
+
+        <copy preservelastmodified="true" todir="${dir.module}/config" failonerror="false">
+            <fileset dir="${dir.src.bean}/"/>
+        </copy>
+
+        <copy preservelastmodified="true" todir="${dir.module}/sql">
+            <fileset dir="${dir.src.sql}/"/>
+        </copy>
+
+        <copy preservelastmodified="true" todir="${dir.module}/lib">
+            <fileset dir="${dir.lib}">
+              <include name="commons-codec-1.3.jar"/>
+              <include name="joda-time-1.4.jar"/>
+              <include name="ldap.jar"/>
+              <include name="${common-pim.jar}"/>
+            </fileset>
+        </copy>
+
+        <jar compress="true" jarfile="${dir.module}/lib/${psync.name}-${psync.version}.jar" update="true">
+            <manifest>
+                <attribute name="Class-Path" value="commons-codec-1.3.jar joda-time-1.4.jar ${common-pim.jar}"/>
+            </manifest>
+            <fileset dir="${dir.output.classes}">
+                <include name="**/*.class"/>
+            </fileset>
+
+        </jar>
+
+        <copy todir="${dir.output}"
+              file="${dir.module}/lib/${psync.name}-${psync.version}.jar"
+              overwrite="true" />
+
+        <delete file="/opt/Funambol/ds-server/modules/${psync.name}-${psync.version}.s4j"/>
+
+        <jar compress="true" jarfile="/opt/Funambol/ds-server/modules/${psync.name}-${psync.version}.s4j">
+            <fileset dir="${dir.module}">
+                <include name="**/*"/>
+            </fileset>
+
+        </jar>
+
+
+    </target>
+
+    <!-- =============================================== -->
+    <!-- CLEAN                                           -->
+    <!-- =============================================== -->
+    <target name="clean">
+        <delete dir="${dir.output}"/>
+    </target>
+
+    <!-- =============================================== -->
+    <!-- CLEAN-MODULE                                    -->
+    <!-- =============================================== -->
+    <target name="clean-module" unless="debug">
+        <echo message="Cleaning ${dir.module}"/>
+        <delete dir="${dir.module}"/>
+    </target>
+
+    <!-- =============================================== -->
+    <!-- JAVADOC                                         -->
+    <!-- =============================================== -->
+    <target name="javadoc" depends="init">
+        <javadoc author="false" destdir="${dir.output.javadoc}" packagenames="br.com.prognus.psync.*" version="false" windowtitle="pSync Connector">
+
+            <classpath>
+                <pathelement location="${dir.lib}/commons-codec-1.3.jar" />
+                <pathelement location="${dir.lib}/commons-lang-2.2.jar"  />
+                <pathelement location="${dir.lib}/funambol-framework.jar"/>
+                <pathelement location="${dir.lib}/funambol-admin-dev.jar"/>
+                <pathelement location="${dir.lib}/joda-time-1.4.jar"     />
+                <pathelement location="${dir.lib}/${pim-common.lib}"     />
+            	<pathelement location="${dir.lib}/ldap.jar"              />
+            </classpath>
+
+            <sourcepath>
+                <pathelement location="${dir.src.java}"/>
+            </sourcepath>
+
+            <doctitle><![CDATA[<img src="logo.gif"> <h1>pSync - Funambol Connector</h1>]]></doctitle>
+            <bottom><![CDATA[<i>Copyright &#169; 2007 Prognus Software Livre.</i>]]></bottom>
+        </javadoc>
+    </target>
+
+
+    <!-- =============================================== -->
+    <!-- DOCS                                            -->
+    <!-- =============================================== -->
+    <target name="docs" depends="init, javadoc"/>
+
+</project>
Index: contrib/funambol/trunk/modules/psync/LICENSE.txt
===================================================================
--- contrib/funambol/trunk/modules/psync/LICENSE.txt	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/LICENSE.txt	(revision 2082)
@@ -0,0 +1,662 @@
+GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license
+for software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are
+designed to take away your freedom to share and change the works.  By
+contrast, our General Public Licenses are intended to guarantee your
+freedom to share and change all versions of a program--to make sure it
+remains free software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public
+License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds
+of works, such as semiconductor masks.
+ 
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further restriction,
+you may remove that term.  If a license document contains a further
+restriction but permits relicensing or conveying under this License, you
+may add to a covered work material governed by the terms of that license
+document, provided that the further restriction does not survive such
+relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have permission
+to link or combine any covered work with a work licensed under version 3
+of the GNU General Public License into a single combined work, and to
+convey the resulting work.  The terms of this License will continue to
+apply to the part which is the covered work, but the work with which it is
+combined will remain governed by version 3 of the GNU General Public
+License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new
+versions will be similar in spirit to the present version, but may differ
+in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero
+General Public License "or any later version" applies to it, you have
+the option of following the terms and conditions either of that
+numbered version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number
+of the GNU Affero General Public License, you may choose any version
+ever published by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that
+proxy's public statement of acceptance of a version permanently
+authorizes you to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                       END OF TERMS AND CONDITIONS
+
+              How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
Index: contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/create_schema.sql
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/create_schema.sql	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/create_schema.sql	(revision 2082)
@@ -0,0 +1,6 @@
+ALTER TABLE phpgw_cal ADD COLUMN last_status char(1) DEFAULT 'N'::bpchar;
+ALTER TABLE phpgw_cal ADD COLUMN last_update int8 DEFAULT (date_part('epoch'::text, ('now'::text)::timestamp(3) with time zone) * (1000)::double precision);
+
+ALTER TABLE phpgw_cc_contact ADD COLUMN last_status char(1) DEFAULT 'N'::bpchar;
+ALTER TABLE phpgw_cc_contact ADD COLUMN last_update int8 DEFAULT (date_part('epoch'::text, ('now'::text)::timestamp(3) with time zone) * (1000)::double precision);
+ALTER TABLE phpgw_cc_contact ADD COLUMN category character varying(20);
Index: contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/init_schema.sql
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/init_schema.sql	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/init_schema.sql	(revision 2082)
@@ -0,0 +1,55 @@
+--
+-- Initialization data for the pSync module
+--
+-- Module structure registration
+--
+delete from fnbl_module where id='psync';
+insert into fnbl_module (id, name, description) values('psync','psync','pSync');
+
+delete from fnbl_connector where id='psync';
+insert into fnbl_connector(id, name, description) values('psync','Funambol pSync Connector','Funambol pSync Connector');
+
+--
+-- SyncSource Types
+--
+delete from fnbl_sync_source_type where id='contact';
+insert into fnbl_sync_source_type(id, description, class, admin_class)
+values('contact','PIM Contact SyncSource','br.com.prognus.psync.engine.source.PIMContactSyncSource','br.com.prognus.psync.admin.PIMContactSyncSourceConfigPanel');
+
+delete from fnbl_sync_source_type where id='calendar';
+insert into fnbl_sync_source_type(id, description, class, admin_class)
+values('calendar','PIM Calendar SyncSource','br.com.prognus.psync.engine.source.PIMCalendarSyncSource','br.com.prognus.psync.admin.PIMCalendarSyncSourceConfigPanel');
+
+
+--
+-- Connectors
+--
+delete from fnbl_connector_source_type where connector='psync' and sourcetype='contact';
+insert into fnbl_connector_source_type(connector, sourcetype) values('psync','contact');
+
+delete from fnbl_connector_source_type where connector='psync' and sourcetype='calendar';
+insert into fnbl_connector_source_type(connector, sourcetype) values('psync','calendar');
+
+--
+-- Module - Connector
+--
+delete from fnbl_module_connector where module='psync' and connector='psync';
+insert into fnbl_module_connector(module, connector) values('psync','psync');
+
+--
+-- SyncSources
+--
+delete from fnbl_client_mapping
+where (sync_source='catalogo' or sync_source='calendario');
+
+delete from fnbl_last_sync
+where (sync_source='catalogo' or sync_source='calendario');
+
+
+delete from fnbl_sync_source where uri='catalogo';
+insert into fnbl_sync_source (uri, config, name, sourcetype)
+  values('catalogo', 'psync/contact/VCardSource.xml','catalogo','contact');
+
+delete from fnbl_sync_source where uri='calendario';
+insert into fnbl_sync_source (uri, config, name, sourcetype)
+  values('calendario', 'psync/calendar/VEventSource.xml','calendario','calendar');
Index: contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/create_schema_antigo.sql
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/create_schema_antigo.sql	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/create_schema_antigo.sql	(revision 2082)
@@ -0,0 +1,6 @@
+ALTER TABLE phpgw_cal ADD COLUMN last_status char(1) DEFAULT 'N'::bpchar;
+ALTER TABLE phpgw_cal ADD COLUMN last_update int8 DEFAULT (date_part('epoch'::text, ('now'::text)::timestamp(3) with time zone) * (1000)::double precision);
+
+ALTER TABLE phpgw_cc_contact ADD COLUMN last_status char(1) DEFAULT 'N'::bpchar;
+ALTER TABLE phpgw_cc_contact ADD COLUMN last_update int8 DEFAULT (date_part('epoch'::text, ('now'::text)::timestamp(3) with time zone) * (1000)::double precision);
+ALTER TABLE phpgw_cc_contact ADD COLUMN category character varying(20);
Index: contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/drop_schema.sql
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/drop_schema.sql	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/sql/postgresql/drop_schema.sql	(revision 2082)
@@ -0,0 +1,6 @@
+ALTER TABLE public.phpgw_cal DROP COLUMN last_status;
+ALTER TABLE public.phpgw_cal DROP COLUMN last_update;
+
+ALTER TABLE public.phpgw_cc_contact DROP COLUMN last_status;
+ALTER TABLE public.phpgw_cc_contact DROP COLUMN last_update;
+ALTER TABLE public.phpgw_cc_contact DROP COLUMN category;
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/model/CalendarWrapper.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/model/CalendarWrapper.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/model/CalendarWrapper.java	(revision 2082)
@@ -0,0 +1,93 @@
+/**
+ *
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.items.model;
+
+import java.sql.Timestamp;
+
+import com.funambol.common.pim.calendar.Calendar;
+
+public class CalendarWrapper {
+
+	// ---------------------------------------------------------------
+	// Properties
+
+	Calendar c;
+
+	public Calendar getCalendar() {
+		return c;
+	}
+
+	String id;
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	String userId;
+
+	public String getUserId() {
+		return userId;
+	}
+
+	Timestamp lastUpdate;
+
+	public void setLastUpdate(Timestamp lastUpdate) {
+		this.lastUpdate = lastUpdate;
+	}
+
+	public Timestamp getLastUpdate() {
+		return lastUpdate;
+	}
+
+	char status;
+
+	public void setStatus(char status) {
+		this.status = status;
+	}
+
+	public char getStatus() {
+		return status;
+	}
+
+	// -------------------------------------------------------------
+	// Constructors
+
+	/**
+	 * Creates a new instance of CalendarWrapper.
+	 * 
+	 * 
+	 * @param id
+	 *            the unique ID of this calendar
+	 * @param userId
+	 *            the ID of the user who owns this calendar
+	 * @param c
+	 *            the wrapped Calendar object
+	 */
+	public CalendarWrapper(String uid, String userId, Calendar c) {
+
+		this.id = uid;
+		this.userId = userId;
+		this.c = c;
+	}
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/model/ContactWrapper.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/model/ContactWrapper.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/model/ContactWrapper.java	(revision 2082)
@@ -0,0 +1,91 @@
+/**
+ *
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.items.model;
+
+import java.sql.Timestamp;
+
+import com.funambol.common.pim.contact.Contact;
+
+public class ContactWrapper {
+
+	// ---------------------------------------------------------------
+	// Properties
+
+	Contact c;
+
+	public Contact getContact() {
+		return this.c;
+	}
+
+	String userId;
+
+	public String getUserId() {
+		return this.userId;
+	}
+
+	String id;
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getId() {
+		return this.id;
+	}
+
+	Timestamp lastUpdate;
+
+	public void setLastUpdate(Timestamp lastUpdate) {
+		this.lastUpdate = lastUpdate;
+	}
+
+	public Timestamp getLastUpdate() {
+		return this.lastUpdate;
+	}
+
+	char status;
+
+	public void setStatus(char status) {
+		this.status = status;
+	}
+
+	public char getStatus() {
+		return this.status;
+	}
+
+	// -------------------------------------------------------------
+	// Constructors
+
+	/**
+	 * Creates a new instance of ContactWrapper.
+	 * 
+	 * @param id
+	 *            the unique ID of this wrapper
+	 * @param userId
+	 *            the ID of the user who owns this contact
+	 * @param c
+	 *            the wrapped Contact object
+	 */
+	public ContactWrapper(String id, String userId, Contact c) {
+
+		this.id = id;
+		this.userId = userId;
+		this.c = c;
+	}
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMCalendarDAO.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMCalendarDAO.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMCalendarDAO.java	(revision 2082)
@@ -0,0 +1,1476 @@
+/**
+ * This class implements methods to access contacts data in domino server data
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.items.dao;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import br.com.prognus.psync.exception.PIMDBAccessException;
+import br.com.prognus.psync.items.model.CalendarWrapper;
+import br.com.prognus.psync.util.Def;
+
+import com.funambol.common.pim.calendar.Calendar;
+import com.funambol.common.pim.calendar.CalendarContent;
+import com.funambol.common.pim.calendar.Event;
+import com.funambol.common.pim.calendar.ExceptionToRecurrenceRule;
+import com.funambol.common.pim.calendar.RecurrencePattern;
+import com.funambol.common.pim.calendar.Reminder;
+import com.funambol.common.pim.utility.TimeUtils;
+import com.funambol.framework.security.Sync4jPrincipal;
+import com.funambol.framework.server.store.NotFoundException;
+import com.funambol.framework.tools.DBTools;
+
+public class PIMCalendarDAO extends PIMEntityDAO {
+
+	// --------------------------------------------------------------- Constants
+
+	private static final String SQL_AND_NO_SUBJECT_IS_SET = "AND ((C.title IS null) OR (C.title = '')) ";
+
+	private static final String SQL_AND_SUBJECT_EQUALS_QUESTIONMARK = "AND C.title = ? ";
+
+	private static final String SQL_AND_NO_dtstart_IS_SET = "AND C.datetime IS null ";
+
+	private static final String SQL_AND_dtstart_EQUALS_QUESTIONMARK = "AND C.datetime = ? ";
+
+	private static final String SQL_AND_NO_DEND_IS_SET = "AND C.edatetime IS null ";
+
+	private static final String SQL_AND_DEND_EQUALS_QUESTIONMARK = "AND C.edatetime = ? ";
+
+	private static final String SQL_EQUALS_QUESTIONMARK = " = ?";
+
+	private static final String SQL_EQUALS_QUESTIONMARK_COMMA = " = ?, ";
+
+	protected static final int SQL_BODY_DIM = 4096;
+
+	protected static final int SQL_LOCATION_DIM = 255;
+
+	protected static final int SQL_SUBJECT_DIM = 255;
+
+	protected static final String SQL_FIELD_ID = "cal_id";
+
+	protected static final String SQL_FIELD_USERID = "owner";
+
+	protected static final String SQL_FIELD_LAST_UPDATE = "last_update";
+
+	protected static final String SQL_FIELD_STATUS = "last_status";
+
+	protected static final String SQL_FIELD_CAL_TYPE = "cal_type";
+
+	protected static final String SQL_FIELD_SENSITIVITY = "is_public";
+
+	protected static final String SQL_FIELD_SUBJECT = "title";
+
+	protected static final String SQL_FIELD_BODY = "description";
+
+	protected static final String SQL_FIELD_LOCATION = "location";
+
+	protected static final String SQL_FIELD_DATE_START = "datetime";
+
+	protected static final String SQL_FIELD_CATEGORY = "category";
+
+	protected static final String SQL_FIELD_DATE_END = "edatetime";
+
+	protected static final String SQL_FIELD_DATE_NOW = "mdatetime";
+
+	private static final String SQL_ORDER_BY_ID = "ORDER BY cal_id ASC";
+
+	private static final String SQL_INSERT_INTO_EXP_PIM_CALENDAR = "INSERT INTO phpgw_cal "
+			+ "(uid, owner, category, datetime, mdatetime, edatetime, cal_type, is_public, title, description, location, ex_participants, last_update, last_status) "
+			+ "VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+	private static final String SQL_INSERT_INTO_EXP_PIM_CALENDAR_USER = "INSERT INTO phpgw_cal_user "
+			+ "(cal_id, cal_login) " + "VALUES " + "(?, ?)";
+
+	private static final String SQL_INSERT_INTO_EXP_PIM_CALENDAR_REPEATS = "INSERT INTO phpgw_cal_repeats "
+			+ "(cal_id, recur_type, recur_use_end, recur_enddate, recur_interval, recur_data, recur_exception) "
+			+ "VALUES " + "(?, ?, ?, ?, ?, ?, ?)";
+
+	private static final String SQL_INSERT_INTO_EXP_PIM_ALARM = "INSERT INTO phpgw_async "
+			+ "(id, next, times, method, data, account_id) "
+			+ "VALUES "
+			+ "(?, ?, ?, ?, ?, ?)";
+
+	private static final String SQL_GET_FNBL_PIM_CALENDAR_EXCEPTION_BY_CALENDAR = "SELECT recur_type, recur_use_end, recur_enddate, recur_interval, recur_data, recur_exception, datetime FROM phpgw_cal_repeats AS R LEFT JOIN  phpgw_cal AS C ON R.cal_id = C.cal_id WHERE C.cal_id = ?";
+
+	private static final String SQL_GET_EXP_PIM_CALENDAR_ID_LIST_BY_USER_TIME_STATUS = "SELECT C.cal_id FROM phpgw_cal_user as U, phpgw_cal as C WHERE C.cal_id = U.cal_id AND U.cal_login = ? AND C.last_update > ? AND C.last_update < ? AND C.last_status = ? AND U.cal_status != 'R' ";
+
+	private static final String SQL_GET_EXP_PIM_CALENDAR_ID_LIST_BY_USER = "SELECT U.cal_id FROM phpgw_cal_user as U, phpgw_cal as C WHERE C.cal_id = U.cal_id AND U.cal_login = ? AND U.cal_status != 'R' ";
+
+	private static final String SQL_GET_STATUS_BY_ID_USER_TIME = "SELECT last_status FROM phpgw_cal WHERE cal_id = ? AND owner = ? AND last_update > ? LIMIT 1";
+
+	private static final String SQL_GET_EXP_PIM_CALENDAR_BY_ID_USERID = "SELECT cal_id, last_update, owner, last_status, location, title, description, datetime, edatetime, is_public, category FROM phpgw_cal WHERE cal_id = ?";
+
+	private static final String SQL_UPDATE_EXP_PIM_CALENDAR_BEGIN = "UPDATE phpgw_cal SET ";
+
+	private static final String SQL_UPDATE_EXP_PIM_CALENDAR_END = " WHERE cal_id = ? AND owner = ? ";
+
+	private Sync4jPrincipal principal = null;
+
+	/*
+	 * @see PIMEntityDAO#PIMEntityDAO(String, String)
+	 */
+	public PIMCalendarDAO(String jndiDataSourceName, Sync4jPrincipal principal) {
+
+		super(jndiDataSourceName, principal.getUsername());
+		log.info("\n\n=> Created new PIMCalendarDAO for data source " + jndiDataSourceName + " and user ID " + userId);
+		this.principal = principal;
+	}
+
+	// ----------------------------------------------------------- Public
+	// methods
+
+	/**
+	 * Adds a calendar. If necessary, a new ID is generated and set in the
+	 * CalendarWrapper.
+	 *
+	 * @param c
+	 *            as a CalendarWrapper object, usually without an ID set.
+	 * @throws PIMDBAccessException
+	 *
+	 * @see CalendarWrapper
+	 */
+	public void addItem(CalendarWrapper cw) throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMCalendarDAO addItem begin");
+
+		Connection 		  con 		= null;
+		PreparedStatement ps 		= null;
+		RecurrencePattern rp 		= null;
+		ResultSet 		  rs 		= null;
+		Reminder          reminder  = null;
+
+		String 	subject 	 	= null;
+		String 	body 		 	= null;
+		String 	location 	 	= null;
+		String 	is_public 	 	= null;
+		String 	category     	= null;
+
+		Date dtstart = null;
+		Date dend    = null;
+		long id_cal  = 0;
+		long dt1     = 0;
+
+		try {
+
+			con = getDataSource().getConnection();
+		 	con.setAutoCommit(false);
+
+			CalendarContent c = cw.getCalendar().getCalendarContent();
+			Timestamp lastUpdate = cw.getLastUpdate();
+
+			lastUpdate = (lastUpdate == null) ? new Timestamp(System.currentTimeMillis()) : lastUpdate;
+
+			// Recebe a data inicial do evento
+			String sd = null;
+			if (c.getDtStart() != null) {
+				sd = c.getDtStart().getPropertyValueAsString(); // data inicial no formato YYYYMMddTHHMMSSZ
+				dtstart = getDateFromString(sd); // data inicial no formato YYYY-MM-dd
+                                dt1 = new Long(Long.toString(dtstart.getTime()).substring(0, 10));
+			}
+
+			// Recebe a data final do evento
+			String ed = null;
+			if (c.getDtEnd() != null) {
+				ed = c.getDtEnd().getPropertyValueAsString();
+				dend = getDateFromString(ed);
+			}
+
+			rp          = c.getRecurrencePattern(); // Recebe a repetiÃ§Ã£o do alarme
+			reminder    = c.getReminder(); // Recebe o alarme do evento
+			body 		= stringFrom(c.getDescription()); // Descricao completa do evento
+			location 	= stringFrom(c.getLocation()); // Localizacao do evento
+			is_public 	= (stringFrom(c.getAccessClass())).equals("2") ? "0" : "1"; // Evento particular ou publico
+			subject 	= stringFrom(c.getSummary()); // Titulo do evento
+			category    = stringFrom(c.getCategories());
+
+			// Verifica o parametro uid necessario para o expresso
+			ps = con.prepareStatement("select config_value from phpgw_config WHERE config_name = 'hostname' AND config_app = 'phpgwapi'");
+			rs = ps.executeQuery();
+			String uid = rs.next() ? ("-@" + rs.getString(1)) : "";
+
+			// Verifica a categoria do evento
+			if(category != null && (!category.equals(""))){
+				category = addCategory(category);
+			}
+
+			// Prepara os dados para inserir o evento no banco de dados
+			ps = con.prepareStatement(SQL_INSERT_INTO_EXP_PIM_CALENDAR);
+			ps.setString(1, uid); // -@hostname
+			ps.setLong(2, Long.parseLong(userId)); // owner
+			ps.setString(3, category); // category
+			ps.setLong(4, new Long(Long.toString(dtstart.getTime()).substring(0, 10))); // datetime
+			ps.setLong(5, new Long(Long.toString(lastUpdate.getTime()).substring(0, 10))); // mdatetime
+			ps.setLong(6, new Long(Long.toString(dend.getTime()).substring(0, 10))); // edatetime
+			ps.setString(7, (rp == null) ? "E" : "M"); // cal_type
+			ps.setLong(8, Long.parseLong(is_public)); // is_plublic
+			ps.setString(9, truncate(subject, SQL_SUBJECT_DIM)); // title
+			ps.setString(10, truncate(body, SQL_BODY_DIM)); // body of event, a little description of it
+			ps.setString(11, truncate(location, SQL_LOCATION_DIM)); // location of the event
+			ps.setString(12, ""); // Participants outside
+			ps.setLong(13, lastUpdate.getTime()); // last update
+			ps.setString(14, String.valueOf(Def.PIM_STATE_NEW)); // status
+			ps.executeUpdate();
+
+			// Verifica o id maximo do evento inserido
+			ps = con.prepareStatement("select max(cal_id) from phpgw_cal limit 1");
+			rs = ps.executeQuery();
+
+			if (rs.next()) {
+				long id = rs.getInt(1);
+				id_cal = (id == 0) ? 1 : id;
+			}
+
+			cw.setId(Long.toString(id_cal));
+
+			ps = con.prepareStatement(SQL_INSERT_INTO_EXP_PIM_CALENDAR_USER);
+			ps.setLong(1, id_cal);
+			ps.setLong(2, Long.parseLong(userId)); // owner
+			ps.executeUpdate();
+
+			// Evento com repetiÃ§Ã£o
+			if (rp != null) {
+				addRecurrencePattern(rp, id_cal, sd);
+			}
+
+log.trace("SERPRO - pimcalendardao - ANTES if (reminder != null && reminder.isActive())");
+
+//Correcao - Se o valor do atributo alarm do calendario eh null, atribui. emerson-faria.nobre@serpro.gov.br
+                        if (reminder != null && reminder.isActive() && reminder.getTime() == null) {
+  	                      String strDate = String.valueOf(dt1 * 1000 - reminder.getMinutes() * 60000);
+                              String dtUTC = com.funambol.common.pim.utility.TimeUtils.convertDateTo(new Date(Long.valueOf(strDate)), com.funambol.common.pim.utility.TimeUtils.TIMEZONE_UTC, com.funambol.common.pim.utility.TimeUtils.PATTERN_UTC);
+                              reminder.setTime(dtUTC);
+                        }
+
+			// Evento com Alarme
+			if (reminder != null && reminder.isActive()) {
+				addReminder(reminder, id_cal);
+			}
+
+log.trace("SERPRO - pimcalendardao - APOS if (reminder != null && reminder.isActive())");
+
+			con.commit();
+ 			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error adding a calendar item: "
+					+ e.getMessage());
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=>Added item with ID " + id_cal);
+		log.info("\n\n=>PIMCalendarDAO addItem end");
+	}
+
+	/**
+	 * Updates a calendar.
+	 *
+	 * @param c
+	 *            as a CalendarWrapper object. If its last update time is null,
+	 *            then it's set to the current time.
+	 * @return the UID of the contact
+	 * @throws PIMDBAccessException
+	 *
+	 * @see CalendarWrapper
+	 */
+	public String updateItem(CalendarWrapper cw) throws PIMDBAccessException, Exception {
+
+		log.info("\n\n=> PIMCalendarDAO updateItem begin");
+
+		Connection 			con 	 = null;
+		PreparedStatement 	ps 		 = null;
+		ResultSet 			rs 		 = null;
+		CalendarContent 	c 		 = null;
+		RecurrencePattern 	rp 	 	 = null;
+		Reminder 			reminder = null;
+
+		long id_cal = 0;
+		long dt1    = 0;
+		long dt2    = 0;
+
+		Date dend 	 = null;
+		Date dtstart = null;
+
+		String body 	 = null;
+		String location  = null;
+		String subject   = null;
+		String is_public = null;
+		String category  = null;
+
+		StringBuffer queryUpdateFunPimCalendar = null;
+
+		try {
+
+			con = getDataSource().getConnection();
+
+
+
+			Timestamp lastUpdate = (cw.getLastUpdate() == null) ? new Timestamp(System.currentTimeMillis()) : cw.getLastUpdate();
+
+			c = cw.getCalendar().getCalendarContent();
+
+			id_cal = Long.parseLong(cw.getId());
+
+			// Verifica se o evento pertence ao dono para ser atualizado
+			ps = con.prepareStatement("SELECT cal_id FROM phpgw_cal WHERE cal_id = ? AND owner = ?");
+			ps.setLong(1, id_cal);
+			ps.setLong(2, Long.parseLong(userId));
+			rs = ps.executeQuery();
+
+			if (rs.next()) {
+
+				rp 			= c.getRecurrencePattern(); // Recebe a repetiÃ§Ã£o do alarme
+				reminder    = c.getReminder(); // Recebe o alarme do evento
+				body 		= stringFrom(c.getDescription()); // DescriÃ§Ã£o completa do evento
+				location 	= stringFrom(c.getLocation()); // Localizacao do evento
+				is_public 	= (stringFrom(c.getAccessClass())).equals("2") ? "0" : "1"; // Evento particular ou publico
+				subject 	= stringFrom(c.getSummary()); // Titulo do evento
+				category    = stringFrom(c.getCategories());
+
+				// Verifica a categoria do evento
+				if(category != null && (!category.equals(""))){
+					category = addCategory(category);
+				} else {
+					category = null;
+				}
+
+				// Recebe a data inicial do evento
+				String sd = null;
+				if (c.getDtStart() != null) {
+					sd = c.getDtStart().getPropertyValueAsString();
+					dtstart = getDateFromString(sd);
+					dt1 = new Long(Long.toString(dtstart.getTime()).substring(0, 10));
+				}
+
+				// Recebe a data final do evento
+				String ed = null;
+				if (c.getDtEnd() != null) {
+					ed = c.getDtEnd().getPropertyValueAsString();
+					dend = getDateFromString(ed);
+					dt2 = new Long(Long.toString(dend.getTime()).substring(0, 10));
+				}
+
+				// Prepara os dados para inserir o evento no banco de dados
+				queryUpdateFunPimCalendar = new StringBuffer();
+				queryUpdateFunPimCalendar.append(SQL_UPDATE_EXP_PIM_CALENDAR_BEGIN);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_CATEGORY).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_DATE_START).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_DATE_NOW).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_DATE_END).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_CAL_TYPE).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_SENSITIVITY).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_SUBJECT).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_BODY).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_LOCATION).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_LAST_UPDATE).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_STATUS).append(SQL_EQUALS_QUESTIONMARK);
+				queryUpdateFunPimCalendar.append(SQL_UPDATE_EXP_PIM_CALENDAR_END);
+
+				ps = con.prepareStatement(queryUpdateFunPimCalendar.toString());
+				ps.setString(1, category); //category
+				ps.setLong(2, dt1); // datetime
+				ps.setLong(3, new Long(Long.toString(lastUpdate.getTime()).substring(0, 10))); // mdatetime
+				ps.setLong(4, dt2); // datetime
+				ps.setString(5, (rp == null) ? "E" : "M"); // cal_type
+				ps.setLong(6, Long.parseLong(is_public)); // is_plublic
+				ps.setString(7, truncate(subject, SQL_SUBJECT_DIM));
+				ps.setString(8, truncate(body, SQL_BODY_DIM));
+				ps.setString(9, truncate(location, SQL_LOCATION_DIM)); // location
+				ps.setLong(10, lastUpdate.getTime()); // last update
+				ps.setString(11, String.valueOf(Def.PIM_STATE_UPDATED)); // status
+				ps.setLong(12, id_cal); // cal_id
+// Correcao - Incluido typecast 'userId' para compatibilizar com o postgresql 8.3 - emerson-faria.nobre@serpro.gov.br - 28/jan/2010
+//			      ps.setString(13, userId); // owner
+			    ps.setLong(13, Long.parseLong(userId));
+				ps.executeUpdate();
+				// Evento com repetiÃ§Ã£o
+				if (rp != null) {
+					addRecurrencePattern(rp, id_cal, sd); // adiciona a repeticao
+				} else {
+					deleteRecurrencePattern(id_cal); // delete a repeticao
+				}
+				// Evento com Alarme
+
+//Correcao - Se o valor do atributo alarm do calendario eh null, atribui. emerson-faria.nobre@serpro.gov.br
+                                if (reminder != null && reminder.isActive() && reminder.getTime() == null) {
+                			String strDate = String.valueOf(dt1 * 1000 - reminder.getMinutes() * 60000);
+					String dtUTC = com.funambol.common.pim.utility.TimeUtils.convertDateTo(new Date(Long.valueOf(strDate)), com.funambol.common.pim.utility.TimeUtils.TIMEZONE_UTC, com.funambol.common.pim.utility.TimeUtils.PATTERN_UTC);
+					reminder.setTime(dtUTC);
+				}
+
+  				if (reminder != null && reminder.isActive() && reminder.getTime() != null) {
+					addReminder(reminder, id_cal); // adiciona o alarme
+				} else {
+					deleteReminder(id_cal); // deleta o alarme
+				}
+			}
+
+ 			con.commit();
+ 			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+
+			throw new PIMDBAccessException(
+					"\n=> Error updating a calendar item: " + e.getMessage());
+
+		} finally {
+
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=>Update item with ID " + id_cal);
+		log.info("\n\n=>PIMCalendarDAO updateItem end");
+
+		return Long.toString(id_cal);
+	}
+
+	/**
+	 * Retrieves the UID list of all calendars belonging to the user of the type
+	 * prescribed by the DAO set-up.
+	 *
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+	public List getAllItems() throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMCalendarDAO getAllItems begin");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		List calendars = new ArrayList();
+		ResultSet rs = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement(SQL_GET_EXP_PIM_CALENDAR_ID_LIST_BY_USER + SQL_ORDER_BY_ID);
+
+//Correcao - Incluido typecast 'userId' para compatibilizar com o postgresql 8.3 - emerson-faria.nobre@serpro.gov.br - 26/feb/2010
+//  	      ps.setString(1, userId);
+            ps.setLong(1, Long.parseLong(userId));
+
+			rs = ps.executeQuery();
+
+			while (rs.next()) {
+				calendars.add(Long.toString(rs.getLong(1))); // It's the first
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error listing contacts. " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=> PIMCalendarDAO getAllItems end");
+
+		return calendars;
+	}
+
+	public CalendarWrapper getItem(String uid) throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start getItem " + uid);
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		ResultSet rs1 = null;
+		CalendarWrapper cw = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			try {
+
+				ps = con.prepareStatement(SQL_GET_EXP_PIM_CALENDAR_BY_ID_USERID);
+				ps.setLong(1, Long.parseLong(uid));
+				rs = ps.executeQuery();
+
+//Correcao -  Incluido typecast cat_ID::text para compatibilizar com o postgreSQL 8.3 - emerson.faria.nobre@serpro.gov.br - 29/jan/2010
+//				  ps = con.prepareStatement("SELECT CT.cat_name FROM phpgw_cal as C, phpgw_categories as CT WHERE C.category = CT.cat_id AND C.cal_id = ?");
+				ps = con.prepareStatement("SELECT CT.cat_name FROM phpgw_cal as C, phpgw_categories as CT WHERE C.category = CT.cat_id::text AND C.cal_id = ?");
+				ps.setLong(1, Long.parseLong(uid));
+				rs1 = ps.executeQuery();
+
+				cw = createCalendar(uid, userId, rs, (rs1.next() ? rs1.getString(1) : ""));
+
+			} catch (SQLException sqle) {
+				throw new SQLException("\n=> Error while adding createCalendar. " + sqle, sqle.getSQLState());
+			}
+
+			try {
+
+				ps = con.prepareStatement(SQL_GET_FNBL_PIM_CALENDAR_EXCEPTION_BY_CALENDAR);
+				ps.setLong(1, Long.parseLong(uid));
+				rs = ps.executeQuery();
+				cw = addPIMCalendarExceptions(cw, rs);
+
+			} catch (SQLException sqle) {
+				throw new SQLException("\n=> Error while adding addPIMCalendarExceptions. " + sqle, sqle.getSQLState());
+			}
+
+			try {
+
+				ps = con.prepareStatement("SELECT next, times, data from phpgw_async WHERE id = ? AND account_id = ?");
+				ps.setString(1, "cal:" + uid + ":0");
+				ps.setLong(2, Long.parseLong(userId));
+				rs = ps.executeQuery();
+				cw = addPIMCalendarAlarm(cw, rs);
+
+			} catch (SQLException sqle) {
+				throw new SQLException("\n=> Error while adding addPIMCalendarAlarm. " + sqle, sqle.getSQLState());
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error retrieving a calendar item: " + e, e);
+		} finally {
+			DBTools.close(con, ps, rs);
+			DBTools.close(con, ps, rs1);
+		}
+
+		return cw;
+	}
+
+	/**
+	 * Removes the calendar with given UID and sets its last_update field,
+	 * provided it has the same userId as this DAO. The deletion is soft
+	 * (reversible).
+	 *
+	 * @param uid
+	 *            corresponds to the id field in the phpgw_cal table
+	 * @throws PIMDBAccessException
+	 */
+	public void removeItem(String uid) throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start removeItem " + uid);
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+ 			con.setAutoCommit(false);
+
+			ps = con.prepareStatement("SELECT cal_id FROM phpgw_cal WHERE cal_id = ? AND owner = ?");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.setLong(2, Long.parseLong(userId));
+			rs = ps.executeQuery();
+
+			if (rs.next()) {
+
+				ps = con.prepareStatement("DELETE FROM phpgw_async WHERE id = ?");
+				ps.setString(1, "cal:" + uid + ":0");
+				ps.executeUpdate();
+
+				ps = con.prepareStatement("DELETE FROM phpgw_cal_repeats WHERE cal_id = ?");
+				ps.setLong(1, Long.parseLong(uid));
+				ps.executeUpdate();
+
+				ps = con.prepareStatement("DELETE FROM phpgw_cal_user WHERE cal_id = ?");
+				ps.setLong(1, Long.parseLong(uid));
+				ps.executeUpdate();
+
+				ps = con.prepareStatement("DELETE FROM phpgw_cal WHERE cal_id = ?");
+				ps.setLong(1, Long.parseLong(uid));
+				ps.executeUpdate();
+
+			} else {
+
+				ps = con.prepareStatement("UPDATE phpgw_cal_user SET cal_status = 'R' WHERE cal_id = ? AND cal_login = ?");
+				ps.setLong(1, Long.parseLong(uid));
+				ps.setLong(2, Long.parseLong(userId));
+				ps.executeUpdate();
+
+			}
+
+ 			con.commit();
+ 			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error deleting calendar.", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+	}
+
+	/**
+	 * Removes a calendar, provided it has the same userId as this DAO. The
+	 * deletion is soft (reversible).
+	 *
+	 * @param calendar
+	 *            whence the UID and the last update Date are extracted
+	 * @throws PIMDBAccessException
+	 */
+	public void removeItem(CalendarWrapper calendar) throws PIMDBAccessException {
+		removeItem(calendar.getId());
+	}
+
+	/**
+	 * Deletes (reversibly) all calendars belonging to the user of the type
+	 * prescribed by the DAO set-up. The last_update field of the (soft-)deleted
+	 * items will be set at a given timestamp.
+	 *
+	 * @throws PIMDBAccessException
+	 */
+	public void removeAllItems() throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start removeAllItems");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+ 			con.setAutoCommit(false);
+
+			ps = con.prepareStatement("DELETE FROM phpgw_async WHERE account_id = ?");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con.prepareStatement("DELETE FROM phpgw_cal_repeats WHERE cal_id IN(SELECT cal_id FROM phpgw_cal_user WHERE cal_login = ?)");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con.prepareStatement("DELETE FROM phpgw_cal_user WHERE cal_id IN(SELECT cal_id FROM phpgw_cal WHERE owner = ?)");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con.prepareStatement("DELETE FROM phpgw_cal WHERE owner = ?");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			con.commit();
+ 			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error deleting calendars.", e);
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	/**
+	 * Retrieves the UID list of the calendars belonging to the user filtered
+	 * according to the given time interval and status. Only the calendars
+	 * corresponding to the type set-up of the DAO are retrieved.
+	 *
+	 * @param since
+	 *            the earliest allowed last-update Timestamp
+	 * @param to
+	 *            the latest allowed last-update Timestamp
+	 * @param status
+	 *            'D' for deleted items, 'N' for new items, 'U' for updated
+	 *            items
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+// Alteracao - Incluido parametro sourceURI - emerson-faria.nobre@serpro.gov.br - 17/set/2009
+	protected List getItemsHavingStatus(Timestamp since, Timestamp to, char status, String sourceURI) throws PIMDBAccessException {
+
+		log.info("\n\n=> Seeking '" + status + "' items of sourceURI:'" + sourceURI + "' in time interval ]" + since + "; " + to + "[");
+
+		Connection 			con       = null;
+		PreparedStatement   ps        = null;
+		List 			    calendars = new ArrayList();
+		ResultSet			rs        = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			if (status == 'D') {
+
+//Correcao - Incluido typecast 'cal_id::text' para compatibilizar com o postgresql 8.3 - emerson-faria.nobre@serpro.gov.br - 28/feb/2010
+//          	  ps = con.prepareStatement("SELECT guid FROM fnbl_client_mapping WHERE sync_source = '" + sourceURI + "' AND guid NOT IN (SELECT C.cal_id FROM phpgw_cal as C, phpgw_cal_user as U WHERE C.cal_id = U.cal_id AND U.cal_login = ?) AND principal = ? ORDER BY guid");
+				ps = con.prepareStatement("SELECT guid FROM fnbl_client_mapping WHERE sync_source = '" + sourceURI + "' AND guid NOT IN (SELECT C.cal_id::text FROM phpgw_cal as C, phpgw_cal_user as U WHERE C.cal_id = U.cal_id AND U.cal_login = ?) AND principal = ? ORDER BY guid");
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setLong(2, this.principal.getId());
+				rs = ps.executeQuery();
+
+			} else {
+
+				ps = con.prepareStatement(SQL_GET_EXP_PIM_CALENDAR_ID_LIST_BY_USER_TIME_STATUS + SQL_ORDER_BY_ID);
+//Correcao - Incluido typecast 'userId' para compatibilizar com o postgresql 8.3 - emerson-faria.nobre@serpro.gov.br - 28/feb/2010
+//    	          ps.setString(1, userId);
+                ps.setLong(1, Long.parseLong(userId));
+				ps.setLong(2, since.getTime());
+				ps.setLong(3, to.getTime());
+				ps.setString(4, String.valueOf(status));
+				rs = ps.executeQuery();
+
+			}
+
+			while (rs.next()) {
+				calendars.add(Long.toString(rs.getLong(1))); // It's the first and only column
+				log.info("\n=> Item found " + rs.getLong(1));
+			}
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new PIMDBAccessException("\n=> Error listing contacts. " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return calendars;
+	}
+
+	/**
+	 * Retrieves the state of the given item, provided it's been modified after
+	 * a certain moment.
+	 *
+	 * @param uid
+	 *            the UID of the item to be checked (as a String object)
+	 * @param since
+	 *            the Timestamp that the item's lastUpdate field is checked
+	 *            against: if the item has been modified before that moment, an
+	 *            "unchanged" state marker is returned
+	 * @throws PIMDBAccessException
+	 * @return a char identifying either one of the 3 standard states ("new",
+	 *         "deleted", "updated") or the special "unchanged" status, all of
+	 *         them as defined in br.com.br.psync.pim.util.Def
+	 */
+	public char getItemState(String uid, Timestamp since) throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start getItemState");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		char status;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement(SQL_GET_STATUS_BY_ID_USER_TIME);
+
+			ps.setLong(1, Long.parseLong(uid));
+			ps.setString(2, userId);
+			ps.setLong(3, since.getTime());
+
+			rs = ps.executeQuery();
+
+			if (!rs.next()) {
+
+				status = Def.PIM_STATE_UNCHANGED;
+				log.info("\n=> Item " + uid + "'s status wasn't retrieved "
+						+ "because the item hasn't been modified since "
+						+ since + " or the item doesn't exist");
+
+			} else {
+
+				status = rs.getString(1).charAt(0);
+				log.info("\n=> Item " + uid + " has status \'" + status + "\'");
+
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error retrieving item state. " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return status;
+	}
+
+	/**
+	 * Retrieves the UID list of the calendars considered to be "twins" of a
+	 * given contact.
+	 *
+	 * @param c
+	 *            the Calendar object representing the calendar whose twins need
+	 *            be found. In the present implementation, only the following
+	 *            data matter:
+	 *            <UL>
+	 *            <LI>date start
+	 *            <LI>date end
+	 *            <LI>location
+	 *            </UL>
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects) that may be empty but not null
+	 */
+	public List getTwinItems(Calendar c) throws PIMDBAccessException {
+
+		log.info("\n\n\n=> PIMCalendarDAO getTwinItems begin");
+
+		LinkedList twins = new LinkedList();
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+
+		try {
+
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			Date dtStart = null;
+			Date dtEnd = null;
+
+			dtStart = getDateFromString(stringFrom(c.getCalendarContent().getDtStart()));
+			dtEnd = getDateFromString(stringFrom(c.getCalendarContent().getDtEnd()));
+
+			StringBuffer sqlGetCalendarTwinList = new StringBuffer(SQL_GET_EXP_PIM_CALENDAR_ID_LIST_BY_USER);
+
+			String subject = stringFrom(c.getCalendarContent().getSummary(), true); // Empty implies null;
+
+			if ("null".equals(subject)) {
+				subject = null;
+			}
+
+			if (subject == null) {
+				sqlGetCalendarTwinList.append(SQL_AND_NO_SUBJECT_IS_SET);
+			} else {
+				sqlGetCalendarTwinList.append(SQL_AND_SUBJECT_EQUALS_QUESTIONMARK);
+			}
+
+			if (dtStart == null) {
+				sqlGetCalendarTwinList.append(SQL_AND_NO_dtstart_IS_SET);
+			} else {
+				sqlGetCalendarTwinList.append(SQL_AND_dtstart_EQUALS_QUESTIONMARK);
+			}
+
+			if (dtEnd == null) {
+				sqlGetCalendarTwinList.append(SQL_AND_NO_DEND_IS_SET);
+			} else {
+				sqlGetCalendarTwinList.append(SQL_AND_DEND_EQUALS_QUESTIONMARK);
+			}
+
+			sqlGetCalendarTwinList.append(SQL_ORDER_BY_ID);
+
+			StringBuilder sb = new StringBuilder(100);
+			sb.append("\nLooking for items having: ");
+
+			if (subject == null || subject.length() == 0) {
+				sb.append("\n> subject: <N/A>");
+			} else {
+				sb.append("\n> subject: '").append(subject).append('\'');
+			}
+			if (dtStart == null) {
+				sb.append("\n> start date: <N/A>");
+			} else {
+				sb.append("\n> start date: ").append(dtStart);
+			}
+			if (dtEnd == null) {
+				sb.append("\n> end date: <N/A>");
+			} else {
+				sb.append("\n> end date: ").append(dtEnd);
+			}
+
+			log.info(sb.toString());
+
+			ps = con.prepareStatement(sqlGetCalendarTwinList.toString());
+
+			int k = 1;
+
+// Correcao - Incluido typecast 'userId' para compatibilizar com o postgresql 8.3 - emerson-faria.nobre@serpro.gov.br - 01/fev/2010
+  	       // ps.setString(k++, userId);
+            ps.setLong(k++, Long.parseLong(userId));
+
+			if (subject != null) {
+				ps.setString(k++, subject);
+			}
+			if (dtStart != null) {
+				ps.setLong(k++, new Long(Long.toString(dtStart.getTime()).substring(0, 10)));
+			}
+			if (dtEnd != null) {
+				ps.setLong(k++, new Long(Long.toString(dtEnd.getTime()).substring(0, 10)));
+			}
+
+			rs = ps.executeQuery();
+
+			long twinId;
+
+			while (rs.next()) {
+
+				twinId = rs.getLong(1); // dend is not relevant in this case
+				log.info("\n\n=> Twin event found: " + twinId);
+
+				twins.add(Long.toString(twinId));
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error retrieving twin. " + e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n\n=> PIMCalendarDAO getTwinItems end");
+
+		return twins;
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	/**
+	 * Creates a ContactWrapper object of Event type from a ResultSet.
+	 *
+	 * @param wrapperId
+	 *            the UID of the wrapper object to be returned
+	 * @param rs
+	 *            the result of the execution of a proper SQL SELECT statement
+	 *            on the phpgw_cal table, with the cursor before its first row
+	 * @return a newly created ContactWrapper initialized with the fields in the
+	 *         result set
+	 * @throws java.sql.SQLException
+	 * @throws NotFoundException
+	 */
+	protected static CalendarWrapper createCalendar(String wrapperId, String user_session ,ResultSet rs, String category) throws NotFoundException, Exception {
+
+		if (!rs.next()) {
+			throw new NotFoundException("\n=> No calendar found.");
+		}
+
+		ResultSetMetaData rsmd = rs.getMetaData();
+		int columnCount = rsmd.getColumnCount();
+
+		String column = null;
+
+		String uid = null;
+		String user = null;
+
+		Date dstart = null;
+		Date dend = null;
+
+		uid = String.valueOf(rs.getLong(SQL_FIELD_ID));
+		user = rs.getString(SQL_FIELD_USERID);
+
+		Calendar cal = new Calendar();
+		Reminder r = new Reminder();
+		CalendarContent c = new Event();
+
+		c.setReminder(r);
+		cal.setEvent((Event) c);
+
+		CalendarWrapper cw = new CalendarWrapper(wrapperId, user, cal);
+
+		for (int i = 1; i <= columnCount; i++) {
+
+			column = rsmd.getColumnName(i);
+
+			if (SQL_FIELD_ID.equalsIgnoreCase(column)) {
+				// Does nothing: field already set at construction time
+			} else if (SQL_FIELD_LAST_UPDATE.equalsIgnoreCase(column)) {
+				cw.setLastUpdate(new Timestamp(rs.getLong(i)));
+			} else if (SQL_FIELD_USERID.equalsIgnoreCase(column)) {
+				// Does nothing: field already set at construction time
+			} else if (SQL_FIELD_CATEGORY.equalsIgnoreCase(column)) {
+				c.getCategories().setPropertyValue(category);
+			} else if (SQL_FIELD_STATUS.equalsIgnoreCase(column)) {
+				cw.setStatus(rs.getString(i).charAt(0));
+			} else if (SQL_FIELD_LOCATION.equalsIgnoreCase(column)) {
+				c.getLocation().setPropertyValue(rs.getString(i));
+			} else if (SQL_FIELD_SUBJECT.equalsIgnoreCase(column)) {
+				c.getSummary().setPropertyValue(rs.getString(i));
+			} else if (SQL_FIELD_BODY.equalsIgnoreCase(column)) {
+				c.getDescription().setPropertyValue(rs.getString(i));
+			} else if (SQL_FIELD_DATE_START.equalsIgnoreCase(column)) {
+				if (rs.getLong(i) != 0) {
+					dstart = new Date(new Timestamp(rs.getLong(i) * 1000).getTime());
+				}
+			} else if (SQL_FIELD_DATE_END.equalsIgnoreCase(column)) {
+				if (rs.getLong(i) != 0) {
+					dend = new Date(new Timestamp(rs.getLong(i) * 1000).getTime());
+				}
+			} else if (SQL_FIELD_SENSITIVITY.equalsIgnoreCase(column)) {
+				Short sensitivity = rs.getShort(i);
+				if (sensitivity == 0) {
+					c.getAccessClass().setPropertyValue(new Short((short) 2));
+				} else {
+					c.getAccessClass().setPropertyValue(new Short((short) 0));
+				}
+			}
+			// Unhandled columns are just ignored
+		}
+
+		c.setAllDay(new Boolean(false));
+
+		if (dstart != null) {
+			c.getDtStart().setPropertyValue(getStringFromDateUTC(dstart));
+		}
+
+		if (dend != null) {
+			c.getDtEnd().setPropertyValue(getStringFromDateUTC(dend));
+		}
+
+		if(!user_session.equals(user)){
+			c.getCategories().setPropertyValue("Compartilhado");
+		}
+
+		return cw;
+	}
+
+	/**
+	 * Converts date in the DB to a date format palm.
+	 *
+	 * @param date
+	 *            it should be in the "yyyyMMdd'T'HHmmss'Z'" format for
+	 *            yyyy-MM-dd HH:MM:SS
+	 * @return a Date object
+	 */
+
+	private static String getStringFromDateUTC(Date date) throws Exception {
+
+		SimpleDateFormat utcDateFormatter = new SimpleDateFormat();
+
+		utcDateFormatter.applyPattern(TimeUtils.PATTERN_UTC);
+		utcDateFormatter.setTimeZone(TimeUtils.TIMEZONE_UTC);
+
+		return utcDateFormatter.format(date);
+	}
+
+	/**
+	 * Converts a String object representing a date into a corresponding Date
+	 * object apt to represent a date in the DB.
+	 *
+	 * @param date
+	 *            it should be in the "yyyyMMdd'T'HHmmss'Z'"
+	 *            format, but will be forced into the right format also if it's
+	 *            in the "yyyyMMdd'T'HHmmss" format (a 'Z' will be appended) or
+	 *            in the "yyyy-MM-dd" format (in this case, the time will be considered 00:00:00)
+	 * @return a Date object
+	 */
+	private static Date getDateFromString(String date) throws ParseException {
+
+		if (date == null || date.length() == 0) {
+			return null;
+		}
+
+		String dateOK = null;
+
+		SimpleDateFormat dateFormatter = new SimpleDateFormat();
+		dateFormatter.applyPattern(TimeUtils.PATTERN_UTC);
+
+		String format = TimeUtils.getDateFormat(date);
+
+		if (format.equals(TimeUtils.PATTERN_YYYY_MM_DD)) {
+
+			dateOK = TimeUtils.convertDateFromInDayFormat(date, "000000", true);
+
+		} else if (format.equals(TimeUtils.PATTERN_UTC_WOZ)) {
+
+			dateOK = date + 'Z'; // the non-all-day formatter wants a 'Z'
+			dateFormatter.setTimeZone(TimeUtils.TIMEZONE_UTC);
+
+		} else {
+
+			dateOK = date; 	// then format should be = TimeUtils.PATTERN_UTC
+			dateFormatter.setTimeZone(TimeUtils.TIMEZONE_UTC);
+		}
+
+		return dateFormatter.parse(dateOK);
+	}
+
+	/**
+	 * Attaches the repetion to the recurrence rule of a calendar on the basis
+	 * of a ResultSet.
+	 *
+	 * @param cw
+	 *            the calendar (as a CalendarWrapper) still lacking information
+	 *            on the exceptions
+	 * @param rs
+	 *            the result of the execution of a proper SQL SELECT statement
+	 *            on the phpgw_cal_repeats table, with the cursor before its
+	 *            first row
+	 * @return the CalendarWrapper object with address information attached
+	 * @throws Exception
+	 */
+	private CalendarWrapper addPIMCalendarExceptions(CalendarWrapper cw, ResultSet rs) throws Exception {
+
+		RecurrencePattern rp = null;
+		short recurrenceType = -1;
+		int interval = 0;
+		short monthOfYear = 0;
+		short dayOfMonth = 0;
+		short dayOfWeekMask = 0;
+		short instance = 0;
+		String startDatePattern = null;
+		String endDatePattern = null;
+		long edate = 0;
+		boolean noend = true;
+		String exceptions_date = null;
+		List exceptions = new Vector();
+
+		if (rs.next()) {
+
+			recurrenceType = Short.parseShort(Long.toString(rs.getLong("recur_type")));
+			interval = Integer.parseInt(Long.toString(rs.getLong("recur_interval")));
+			dayOfWeekMask = Short.parseShort(Long.toString(rs.getLong("recur_data")));
+			startDatePattern = getStringFromDateUTC(new Date(new Timestamp(rs .getLong("datetime") * 1000).getTime()));
+			edate = rs.getLong("recur_enddate");
+			exceptions_date = rs.getString("recur_exception");
+
+			if (edate != 0) {
+				endDatePattern = getStringFromDateUTC(new Date(new Timestamp(edate * 1000).getTime()));
+				noend = false;
+			}
+
+			StringTokenizer exception_date = new StringTokenizer(exceptions_date, ",");
+
+			while (exception_date.hasMoreTokens()) {
+				String date = exception_date.nextToken();
+				SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+				ExceptionToRecurrenceRule etrr = new ExceptionToRecurrenceRule(false, sdf.format(new Date(new Long(date) * 1000)).concat("Z"));
+				exceptions.add(etrr);
+			}
+
+			switch (recurrenceType) {
+			case 1: {
+				recurrenceType = 0;
+				break;
+			}
+			case 2: {
+				recurrenceType = 1;
+				break;
+			}
+			case 3: {
+				recurrenceType = 2;
+				break;
+			}
+			case 4: {
+				recurrenceType = 3;
+				break;
+			}
+			case 5: {
+				recurrenceType = 5;
+				break;
+			}
+			default:
+				break;
+			}
+
+			// Repeticao do evento
+			rp = new RecurrencePattern(recurrenceType, (interval == 0 ? 1 : interval), monthOfYear, dayOfMonth, dayOfWeekMask, instance, startDatePattern, endDatePattern, noend);
+			rp.setExceptions(exceptions);
+			cw.getCalendar().getCalendarContent().setRecurrencePattern(rp);
+		}
+
+		return cw;
+	}
+
+	/**
+	 * Attaches the alarm to the recurrence rule of a calendar on the basis of a
+	 * ResultSet.
+	 *
+	 * @param cw
+	 *            the calendar (as a CalendarWrapper) still lacking information
+	 *            on the exceptions
+	 * @param rs
+	 *            the result of the execution of a proper SQL SELECT statement
+	 *            on the phpgw_async table, with the cursor before its first row
+	 * @return the CalendarWrapper object with address information attached
+	 * @throws Exception
+	 */
+	private CalendarWrapper addPIMCalendarAlarm(CalendarWrapper cw, ResultSet rs) throws Exception {
+
+		if (rs.next()) {
+
+			Reminder rt = null;
+
+			Connection con = null;
+			PreparedStatement ps = null;
+			ResultSet rs1 = null;
+
+			Date datea = null;
+			Date dateb = null;
+			long difDatas = 0;
+
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement("SELECT datetime from phpgw_cal WHERE cal_id = ?");
+			ps.setLong(1, Long.parseLong(cw.getId()));
+			rs1 = ps.executeQuery();
+
+			if (rs1.next()) {
+
+				datea = new Date(new Long(rs.getLong("next") * 1000));
+				dateb = new Date(new Long(rs1.getLong("datetime") * 1000));
+				difDatas = dateb.getTime() - datea.getTime();
+
+				rt = new Reminder();
+				rt.setActive(true);
+				rt.setMinutes(new Integer(Integer.parseInt(new Long(Math.abs(difDatas / (60 * 1000))).toString())));
+// Correcao: Para parar de desaparecer o alarme apos algumas sincronizacoes. 
+// Atribui data-hora para o campo Calendar.Reminder.Time - emerson-faria.nobre@serpro.gov.br - 03/08/2009
+// Comentada a linha abaixo ate que a alteracao do codigo PHP do Expresso seja colocada em producao - 29/09/2009
+//  				rt.setTime(com.funambol.common.pim.utility.TimeUtils.convertDateTo(datea, com.funambol.common.pim.utility.TimeUtils.TIMEZONE_UTC, com.funambol.common.pim.utility.TimeUtils.PATTERN_UTC));
+
+				cw.getCalendar().getCalendarContent().setReminder(rt);
+
+			}
+
+			DBTools.close(con, ps, rs1);
+		}
+
+		return cw;
+	}
+
+	private String addCategory(String category) throws Exception {
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		String id_category = null;
+
+		try {
+
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement("SELECT cat_id from phpgw_categories WHERE cat_owner = -1 AND cat_appname = 'calendar' AND cat_name = ?");
+			ps.setString(1, category);
+			rs = ps.executeQuery();
+
+			if(rs.next()){
+				id_category = rs.getString(1);
+			} else {
+
+				ps = con.prepareStatement("SELECT cat_id from phpgw_categories WHERE cat_owner = ? AND cat_appname = 'calendar' AND cat_name = ?");
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setString(2, category);
+				rs = ps.executeQuery();
+
+				if(rs.next()){
+					id_category = rs.getString(1);
+				}
+			}
+
+
+			if(id_category == null){
+
+				ps = con.prepareStatement("INSERT INTO phpgw_categories (cat_owner, cat_access, cat_appname, cat_name, cat_description, cat_data, last_mod) VALUES (?, 'public', 'calendar', ?, '', 'N;', ?)");
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setString(2, category);
+				ps.setLong(3, new Long(Long.toString(new Timestamp(System.currentTimeMillis()).getTime()).substring(0, 10)));
+				ps.executeUpdate();
+
+				ps = con.prepareStatement("SELECT cat_id from phpgw_categories WHERE cat_owner = ? AND cat_appname = 'calendar' AND cat_name = ?");
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setString(2, category);
+				rs = ps.executeQuery();
+
+				rs.next();
+
+				id_category = rs.getString(1);
+
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error search category <" + category + "> " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return id_category;
+	}
+
+	private void addRecurrencePattern(RecurrencePattern rp, long id_cal, String sd) throws Exception {
+
+		Connection 		  con 		= null;
+		PreparedStatement ps 		= null;
+
+		String  dayOfWeekMask 	= null;
+		String  endDatePattern 	= null;
+		String  exception_date 	= "";
+		String  exception_time 	= "";
+
+		int   interval 		 = 0;
+		short recurrenceType = -1;
+		short recur_type 	 = 0;
+
+		try {
+
+			con = getDataSource().getConnection();
+
+			interval = rp.getInterval();
+			recurrenceType = rp.getTypeId();
+			dayOfWeekMask = String.valueOf(rp.getDayOfWeekMask());
+			endDatePattern = rp.getEndDatePattern();
+
+			if (rp.isNoEndDate()) {
+				endDatePattern = "0";
+			} else {
+				endDatePattern = (Long.toString(getDateFromString(endDatePattern).getTime()).substring(0, 10));
+			}
+
+			List exceptions = rp.getExceptions();
+
+			if (!exceptions.isEmpty()) {
+
+				ExceptionToRecurrenceRule etrr = null;
+				exception_time = sd.substring(9, 16); // hora inicial do evento
+
+				int i = 0;
+				int size = exceptions.size();
+
+				for (; i < size - 1; i++) {
+
+					etrr = (ExceptionToRecurrenceRule) exceptions.get(i);
+					exception_date = exception_date + (Long.toString(getDateFromString(etrr.getDate().substring(0, 9).concat(exception_time)).getTime()).substring(0, 10)) + ",";
+				}
+
+				etrr = (ExceptionToRecurrenceRule) exceptions.get(i);
+				exception_date = exception_date + (Long.toString(getDateFromString(etrr.getDate().substring(0, 9).concat(exception_time)).getTime()).substring(0, 10));
+			}
+
+			// Seta o tipo de repetiÃ§Ã£o
+			if (recurrenceType != -1) {
+
+				switch (recurrenceType) {
+				case 0: {
+					recur_type = 1;
+					break;
+				}
+				case 1: {
+					recur_type = 2;
+					break;
+				}
+				case 2: {
+					recur_type = 3;
+					break;
+				}
+				case 3: {
+					recur_type = 4;
+					break;
+				}
+				case 5: {
+					recur_type = 5;
+					break;
+				}
+				default:
+					break;
+				}
+			}
+
+			// Apaga a repeticao do evento se existir
+			deleteRecurrencePattern(id_cal);
+
+			// Insere no banco de dados a repeticao do evento
+			ps = con.prepareStatement(SQL_INSERT_INTO_EXP_PIM_CALENDAR_REPEATS);
+			ps.setLong(1, id_cal);
+			ps.setLong(2, recur_type);
+			ps.setLong(3, 0);
+			ps.setLong(4, new Long(endDatePattern));
+			ps.setLong(5, interval);
+			ps.setLong(6, new Long(dayOfWeekMask));
+			ps.setString(7, exception_date);
+			ps.executeUpdate();
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error event repetitive  " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	private void deleteRecurrencePattern(long id_cal) throws Exception {
+
+		Connection 		  con 		= null;
+		PreparedStatement ps 		= null;
+
+		try {
+
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement("DELETE FROM phpgw_cal_repeats WHERE cal_id = ?");
+			ps.setLong(1, id_cal);
+			ps.executeUpdate();
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error delete event repetitive  " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	private void addReminder(Reminder reminder, long id_cal) throws Exception {
+
+		Connection 		  con 		= null;
+		PreparedStatement ps 		= null;
+
+		try {
+			con = getDataSource().getConnection();
+
+			int  minutes = 0;
+			long date    = 0;
+
+			minutes = (reminder.getMinutes() * 60);
+			date = new Long(Long.toString(getDateFromString(reminder.getTime()).getTime()).substring(0, 10));
+
+			// Apaga o alarme do evento
+			deleteReminder(id_cal);
+
+			// Insere no banco de dados o alarme do evento
+			ps = con.prepareStatement(SQL_INSERT_INTO_EXP_PIM_ALARM);
+			ps.setString(1, "cal:" + id_cal + ":0"); // id
+			ps.setLong(2, date); // next
+			ps.setString(3, "i:" + date + ";"); // times
+			ps.setString(4, "calendar.bocalendar.send_alarm"); // method
+			ps.setString(5, "a:5:{s:4:\"time\";i:" + date
+							+ ";s:6:\"offset\";i:" + minutes + ";s:5:\"owner\";i:"
+							+ userId + ";s:7:\"enabled\";i:1;s:6:\"cal_id\";s:"
+							+ new Long(id_cal).toString().length() + ":\"" + id_cal
+							+ "\";}"); // data
+			ps.setLong(6, Long.parseLong(userId)); // account_id
+			ps.executeUpdate();
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error event alarm  " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	private void deleteReminder(long id_cal) throws Exception {
+
+		Connection 		  con 		= null;
+		PreparedStatement ps 		= null;
+
+		try {
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement("DELETE FROM phpgw_async WHERE id = ? AND account_id = ?");
+			ps.setString(1, "cal:" + id_cal + ":0");
+			ps.setLong(2, Long.parseLong(userId));
+			ps.executeUpdate();
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error delete event alarm  " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMContactDAO.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMContactDAO.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMContactDAO.java	(revision 2082)
@@ -0,0 +1,1345 @@
+/**
+ * This class implements methods to access PIM contact data in a data store.
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.items.dao;
+
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import br.com.prognus.psync.exception.PIMDBAccessException;
+import br.com.prognus.psync.items.model.ContactWrapper;
+import br.com.prognus.psync.util.Def;
+
+import com.funambol.common.pim.contact.BusinessDetail;
+import com.funambol.common.pim.contact.Contact;
+import com.funambol.common.pim.contact.Email;
+import com.funambol.common.pim.contact.Name;
+import com.funambol.common.pim.contact.PersonalDetail;
+import com.funambol.common.pim.contact.Phone;
+import com.funambol.framework.security.Sync4jPrincipal;
+import com.funambol.framework.server.store.NotFoundException;
+import com.funambol.framework.tools.DBTools;
+
+public class PIMContactDAO extends PIMEntityDAO {
+
+	// --------------------------------------------------------------- Constants
+
+	private static final String SQL_ORDER_BY_ID = "ORDER BY id_contact";
+
+	private static final String SQL_GET_CONTACT_ID_LIST = "SELECT id_contact FROM phpgw_cc_contact ";
+
+	private static final String SQL_GET_CONTACT_ID_LIST_BY_USER = SQL_GET_CONTACT_ID_LIST
+			+ "WHERE id_owner = ?";
+
+	private static final String SQL_GET_CONTACT_BY_ID_USER = "SELECT id_contact, id_owner, last_update, last_status, given_names, family_names, birthdate, category FROM phpgw_cc_contact WHERE id_contact = ? AND id_owner = ? LIMIT 1";
+
+	private static final String SQL_GET_CONTACT_ITEM_BY_ID = "SELECT connection_name, connection_value, id_typeof_contact_connection FROM phpgw_cc_connections AS CO LEFT JOIN phpgw_cc_contact_conns AS CC ON CO.id_connection = CC.id_connection WHERE id_contact = ?";
+
+	private static final String SQL_GET_STATUS_BY_ID_USER_TIME = "SELECT last_status FROM phpgw_cc_contact WHERE id_contact = ? AND id_owner = ? AND last_update > ? LIMIT 1 ";
+
+	private static final String SQL_GET_FNBL_PIM_CONTACT_ID_LIST_BY_USER_TIME_STATUS = SQL_GET_CONTACT_ID_LIST
+			+ "WHERE id_owner = ? AND last_update > ? AND last_update < ? AND last_status = ? ";
+
+	private static final String SQL_GET_CONTACT_TWIN_ID_LIST = "SELECT id_contact "
+			+ "FROM phpgw_cc_contact "
+			+ "WHERE (id_owner = ?) "
+			+ "AND ((given_names is null AND (?) = '') "
+			+ "OR (given_names = ?)) "
+			+ "AND ((family_names is null AND (?) = '') "
+			+ "OR (family_names = ?)) ";
+
+	private static final String SQL_INSERT_INTO_FNBL_PIM_CONTACT = "INSERT INTO phpgw_cc_contact "
+			+ "(id_contact, id_owner, id_status, given_names, family_names, names_ordered, birthdate, category, last_update, last_status ) "
+			+ "VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ";
+
+	private static final String SQL_INSERT_INTO_CONTACT_ITEM = "INSERT INTO phpgw_cc_connections "
+			+ "(id_connection, connection_name, connection_value, connection_is_default) "
+			+ "VALUES (?, ?, ?, ?) ";
+
+	private static final String SQL_ID_MAX_CONNECTIONS = "select max(id_connection) from phpgw_cc_connections";
+
+	private static final String SQL_INSERT_INTO_FNBL_CONNECTIONS = "INSERT INTO phpgw_cc_contact_conns (id_contact, id_connection, id_typeof_contact_connection) VALUES (?, ?, ?)";
+
+	private static final String SQL_UPDATE_FNBL_PIM_CONTACT_BEGIN = "UPDATE phpgw_cc_contact SET ";
+
+	private static final String SQL_UPDATE_FNBL_PIM_CONTACT_END = " WHERE id_contact = ? AND id_owner = ? ";
+
+	private static final String SQL_EQUALS_QUESTIONMARK = " = ?";
+
+	private static final String SQL_EQUALS_QUESTIONMARK_COMMA = " = ?, ";
+
+	protected static final String SQL_FIELD_ID = "id_contact";
+
+	protected static final String SQL_FIELD_USERID = "id_owner";
+
+	protected static final String SQL_FIELD_LAST_UPDATE = "last_update";
+
+	protected static final String SQL_FIELD_STATUS = "last_status";
+
+	protected static final String SQL_FIELD_FIRST_NAME = "given_names";
+
+	protected static final String SQL_FIELD_LAST_NAME = "family_names";
+
+	protected static final String SQL_FIELD_NAME = "names_ordered";
+
+	protected static final String SQL_FIELD_NICK = "alias";
+
+	protected static final String SQL_FIELD_BIRTHDAY = "birthdate";
+
+	protected static final String SQL_FIELD_CATEGORY = "category";
+
+	protected static final int SQL_FIRSTNAME_DIM = 49;
+
+	protected static final int SQL_LASTNAME_DIM = 50;
+
+	protected static final int SQL_EMAIL_DIM = 50;
+
+	protected static final int SQL_PHONE_DIM = 50;
+
+	protected static final int SQL_CATEGORY_DIM = 20;
+
+	protected static final String TYPE_EMAIL_1_ADDRESS = "Principal";
+
+	protected static final String FIELD_EMAIL_1_ADDRESS = "Email1Address";
+
+	protected static final String TYPE_EMAIL_2_ADDRESS = "Alternativo";
+
+	protected static final String FIELD_EMAIL_2_ADDRESS = "OtherEmail2Address";
+
+	protected static final String TYPE_PRIMARY_TELEPHONE_NUMBER = "Principal";
+
+	protected static final String FIELD_PRIMARY_TELEPHONE_NUMBER = "PrimaryTelephoneNumber";
+
+	protected static final String TYPE_HOME_TELEPHONE_NUMBER = "Residencial";
+
+	protected static final String FIELD_HOME_TELEPHONE_NUMBER = "HomeTelephoneNumber";
+
+	protected static final String TYPE_MOBILE_TELEPHONE_NUMBER = "Celular";
+
+	protected static final String FIELD_MOBILE_TELEPHONE_NUMBER = "MobileTelephoneNumber";
+
+	protected static final String TYPE_BUSINESS_TELEPHONE_NUMBER = "Comercial";
+
+	protected static final String FIELD_BUSINESS_TELEPHONE_NUMBER = "BusinessTelephoneNumber";
+
+	protected static final String TYPE_BUSINESS_FAX_NUMBER = "Fax";
+
+	protected static final String FIELD_BUSINESS_FAX_NUMBER = "BusinessFaxNumber";
+
+	protected static final String TYPE_PAGER_NUMBER = "Pager";
+
+	protected static final String FIELD_PAGER_NUMBER = "PagerNumber";
+
+	protected static final String SQL_FIELD_ITEM_NAME = "connection_name";
+
+	protected static final String SQL_FIELD_ITEM_VALUE = "connection_value";
+
+	protected static final String SQL_FIELD_ITEM_TYPE = "id_typeof_contact_connection";
+
+	private Sync4jPrincipal principal = null;
+
+	// -------------------------------------------------------------
+	// Constructors
+
+	/**
+	 * @see PIMEntityDAO#PIMEntityDAO(String, String)
+	 */
+	public PIMContactDAO(String jndiDataSourceName, Sync4jPrincipal principal) {
+
+		super(jndiDataSourceName, principal.getUsername());
+
+		log.info("\n\n=> Created new PIMContactDAO for data source "
+				+ jndiDataSourceName + " and user ID LDAP " + userId
+				+ " user login " + principal.getUsername() + "\n");
+
+		this.principal = principal;
+	}
+
+	// ----------------------------------------------------------- Public
+	// methods
+
+	/**
+	 * Adds a contact. If necessary, a new ID is generated and set in the
+	 * ContactWrapper.
+	 *
+	 * @param c
+	 *            as a ContactWrapper object, usually without an ID set.
+	 * @throws PIMDBAccessException
+	 * @throws SQLException
+	 *
+	 * @see ContactWrapper
+	 */
+	public void addItem(ContactWrapper cw) throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMContactDAO addItem begin\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+
+		long id_contact = 0;
+		long id_connection = 0;
+
+		PersonalDetail personalDetail = null;
+		BusinessDetail businessDetail = null;
+
+		Name name = null;
+		Phone phone = null;
+		Email email = null;
+
+		List<Email> emails = new ArrayList();
+		List<Phone> phones = new ArrayList();
+
+		String phoneType = null;
+		String firstName = null;
+		String lastName = null;
+		String birthday = null;
+		String type = null;
+		String category = null;
+
+		StringBuffer fullName = null;
+		Date anniversary = null;
+
+		try {
+
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			Contact c = cw.getContact();
+			Timestamp lastUpdate = cw.getLastUpdate();
+
+			personalDetail = c.getPersonalDetail();
+			businessDetail = c.getBusinessDetail();
+			name = c.getName();
+
+			lastUpdate = (lastUpdate == null) ? new Timestamp(System
+					.currentTimeMillis()) : lastUpdate;
+
+			if (personalDetail != null) {
+				emails.addAll(personalDetail.getEmails());
+				phones.addAll(personalDetail.getPhones());
+				birthday = personalDetail.getBirthday();
+				category = stringFrom(c.getCategories());
+			}
+
+			if (businessDetail != null) {
+				emails.addAll(businessDetail.getEmails());
+				phones.addAll(businessDetail.getPhones());
+			}
+
+			if (name != null) {
+				firstName = stringFrom(name.getFirstName());
+				lastName = stringFrom(name.getLastName());
+			}
+
+			// consulta o id maximo da tabela contatos
+			ps = con
+					.prepareStatement("select max(id_contact) from phpgw_cc_contact  limit 1");
+			rs = ps.executeQuery();
+
+			if (rs.next()) {
+				long id = rs.getInt(1);
+				id_contact = (id == 0) ? 1 : ++id;
+			}
+
+			cw.setId(Long.toString(id_contact));
+
+			// Formata o nome completo do contato
+			fullName = new StringBuffer();
+
+			if (firstName != null && firstName.length() > 0)
+				fullName.append(truncate(firstName, SQL_FIRSTNAME_DIM));
+			if (lastName != null && lastName.length() > 0) {
+				if (firstName != null && firstName.length() > 0
+						&& lastName != null && lastName.length() > 0)
+					fullName.append(" " + truncate(lastName, SQL_LASTNAME_DIM));
+				else
+					fullName.append(truncate(lastName, SQL_LASTNAME_DIM));
+			}
+
+			// Formata a data de aniversario do contato
+			if ((birthday != null) && (!birthday.equals(""))) {
+				anniversary = new java.sql.Date((new SimpleDateFormat(
+						"yyyy-MM-dd").parse(birthday)).getTime());
+			}
+
+			// Prepara os dados para inserir o contato no banco de dados
+			ps = con.prepareStatement(SQL_INSERT_INTO_FNBL_PIM_CONTACT);
+			ps.setLong(1, id_contact); // id_contact
+			ps.setLong(2, Long.parseLong(userId)); // id_owner
+			ps.setLong(3, 1); // id_status
+			ps.setString(4, truncate(firstName, SQL_FIRSTNAME_DIM)); // given_names
+			ps.setString(5, truncate(lastName, SQL_LASTNAME_DIM)); // family_names
+			ps.setString(6, fullName.toString()); // names_ordered
+			ps.setDate(7, anniversary); // birthdate
+			ps.setString(8, truncate(category, SQL_CATEGORY_DIM)); // category
+			ps.setLong(9, lastUpdate.getTime()); // last_update
+			ps.setString(10, String.valueOf(Def.PIM_STATE_NEW)); // last_status
+			ps.executeUpdate();
+
+			// emails
+			if (!emails.isEmpty()) {
+
+				for (int i = 0, l = emails.size(); i < l; i++) {
+
+					email = emails.get(i);
+					Boolean type_email;
+
+					if ((FIELD_EMAIL_1_ADDRESS).equals(email.getEmailType())) {
+						type = TYPE_EMAIL_1_ADDRESS;
+						type_email = true;
+					} else if ((FIELD_EMAIL_2_ADDRESS).equals(email
+							.getEmailType())) {
+						type = TYPE_EMAIL_2_ADDRESS;
+						type_email = false;
+					} else {
+						continue; // Unknown property: saves nothing
+					}
+
+					String emailValue = email.getPropertyValueAsString();
+
+					if (emailValue != null && emailValue.length() != 0) {
+
+						if (emailValue.length() > SQL_EMAIL_DIM) {
+							emailValue = emailValue.substring(0, SQL_EMAIL_DIM);
+						}
+
+						// Verifica o id maximo da tabela phpgw_cc_connections
+						ps = con.prepareStatement(SQL_ID_MAX_CONNECTIONS);
+						rs = ps.executeQuery();
+
+						if (rs.next()) {
+							long id = rs.getInt(1);
+							id_connection = (id == 0) ? 1 : ++id;
+						}
+
+						// Insere os emails do contato na tabela
+						// phpgw_cc_connections
+						ps = con.prepareStatement(SQL_INSERT_INTO_CONTACT_ITEM);
+						ps.setLong(1, id_connection);
+						ps.setString(2, type);
+						ps.setString(3, emailValue);
+						ps.setBoolean(4, type_email);
+						ps.executeUpdate();
+
+						// Insere as referencias de emails do contato na tabela
+						// phpgw_cc_connections
+						ps = con
+								.prepareStatement(SQL_INSERT_INTO_FNBL_CONNECTIONS);
+						ps.setLong(1, id_contact);
+						ps.setLong(2, id_connection);
+						ps.setLong(3, 1);
+						ps.executeUpdate();
+					}
+
+				}
+			}
+
+			// phones
+			if (!phones.isEmpty()) {
+
+				Boolean type_phone = true;
+
+				for (int i = 0, l = phones.size(); i < l; i++) {
+
+					phone = phones.get(i);
+					phoneType = phone.getPhoneType();
+
+					if ((FIELD_PRIMARY_TELEPHONE_NUMBER).equals(phoneType)) {
+						type = TYPE_PRIMARY_TELEPHONE_NUMBER;
+					} else if ((FIELD_HOME_TELEPHONE_NUMBER).equals(phoneType)) {
+						type = TYPE_HOME_TELEPHONE_NUMBER;
+					} else if ((FIELD_MOBILE_TELEPHONE_NUMBER)
+							.equals(phoneType)) {
+						type = TYPE_MOBILE_TELEPHONE_NUMBER;
+					} else if ((FIELD_BUSINESS_TELEPHONE_NUMBER)
+							.equals(phoneType)) {
+						type = TYPE_BUSINESS_TELEPHONE_NUMBER;
+					} else if ((FIELD_BUSINESS_FAX_NUMBER).equals(phoneType)) {
+						type = TYPE_BUSINESS_FAX_NUMBER;
+					} else if ((FIELD_PAGER_NUMBER).equals(phoneType)) {
+						type = TYPE_PAGER_NUMBER;
+					} else {
+						continue; // Unknown property: saves nothing
+					}
+
+					String phoneValue = phone.getPropertyValueAsString();
+
+					if (phoneValue != null && phoneValue.length() != 0) {
+
+						if (phoneValue.length() > SQL_PHONE_DIM) {
+							phoneValue = phoneValue.substring(0, SQL_PHONE_DIM);
+						}
+
+						ps = con.prepareStatement(SQL_ID_MAX_CONNECTIONS);
+						rs = ps.executeQuery();
+
+						if (rs.next()) {
+							long id = rs.getInt(1);
+							id_connection = (id == 0) ? 1 : ++id;
+						}
+
+						// Insere os telefones do contato na tabela
+						// phpgw_cc_connections
+						ps = con.prepareStatement(SQL_INSERT_INTO_CONTACT_ITEM);
+						ps.setLong(1, id_connection);
+						ps.setString(2, type);
+						ps.setString(3, phoneValue);
+						ps.setBoolean(4, type_phone);
+						ps.executeUpdate();
+
+						type_phone = false; // os demais telefones serao setados
+						// como nao padrao
+
+						// Insere as referencias de telefones do contato na
+						// tabela phpgw_cc_connections
+						ps = con
+								.prepareStatement(SQL_INSERT_INTO_FNBL_CONNECTIONS);
+						ps.setLong(1, id_contact);
+						ps.setLong(2, id_connection);
+						ps.setLong(3, 2);
+						ps.executeUpdate();
+					}
+
+				}
+			}
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error adding contact.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=> Added item with ID " + id_contact + "\n");
+		log.info("\n\n=> PIMContactDAO addItem end\n");
+	}
+
+	/**
+	 * Updates a contact.
+	 *
+	 * @param c
+	 *            as a ContactWrapper object. If its last update time is null,
+	 *            then it's set to the current time.
+	 * @return the UID of the contact
+	 * @throws PIMDBAccessException
+	 *
+	 * @see ContactWrapper
+	 */
+	public String updateItem(ContactWrapper cw) throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMContactDAO updateItem begin\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+
+		long id_connection = 0;
+		long id_contact = 0;
+
+		PersonalDetail personalDetail = null;
+		BusinessDetail businessDetail = null;
+
+		Name name = null;
+		Phone phone = null;
+		Email email = null;
+		String type = null;
+
+		List<Email> emails = new ArrayList();
+		List<Phone> phones = new ArrayList();
+
+		String phoneType = null;
+		StringBuffer queryUpdateFunPimContact = null;
+
+		String firstName = null;
+		String lastName = null;
+		StringBuffer fullName = null;
+		String birthday = null;
+		String category = null;
+		Date anniversary = null;
+
+		try {
+
+			Timestamp lastUpdate = (cw.getLastUpdate() == null) ? new Timestamp(
+					System.currentTimeMillis())
+					: cw.getLastUpdate();
+
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			Contact c = cw.getContact();
+
+			personalDetail = c.getPersonalDetail();
+			businessDetail = c.getBusinessDetail();
+			name = c.getName();
+			id_contact = Long.parseLong(cw.getId());
+
+			if (personalDetail != null) {
+				emails.addAll(personalDetail.getEmails());
+				phones.addAll(personalDetail.getPhones());
+				birthday = personalDetail.getBirthday();
+				category = stringFrom(c.getCategories());
+			}
+
+			if (businessDetail != null) {
+				emails.addAll(businessDetail.getEmails());
+				phones.addAll(businessDetail.getPhones());
+			}
+
+			if (name != null) {
+				firstName = stringFrom(name.getFirstName());
+				lastName = stringFrom(name.getLastName());
+			}
+
+			if (firstName != null && firstName.length() > SQL_FIRSTNAME_DIM) {
+				firstName = firstName.substring(0, SQL_FIRSTNAME_DIM);
+			}
+			if (lastName != null && lastName.length() > SQL_LASTNAME_DIM) {
+				lastName = lastName.substring(0, SQL_LASTNAME_DIM);
+			}
+			if (category != null && category.length() > SQL_CATEGORY_DIM) {
+				category = category.substring(0, SQL_CATEGORY_DIM);
+			}
+
+			// Formata o nome completo do contato
+			fullName = new StringBuffer();
+
+			if (firstName != null && firstName.length() > 0)
+				fullName.append(truncate(firstName, SQL_FIRSTNAME_DIM));
+			if (lastName != null && lastName.length() > 0) {
+				if (firstName != null && firstName.length() > 0
+						&& lastName != null && lastName.length() > 0)
+					fullName.append(" " + truncate(lastName, SQL_LASTNAME_DIM));
+				else
+					fullName.append(truncate(lastName, SQL_LASTNAME_DIM));
+			}
+
+			// Formata a data de aniversario do contato
+			if ((birthday != null) && (!birthday.equals(""))) {
+				anniversary = new java.sql.Date((new SimpleDateFormat(
+						"yyyy-MM-dd").parse(birthday)).getTime());
+			} else {
+				anniversary = null;
+			}
+
+			// Prepara os dados do contato para serem atualizados no banco de
+			// dados
+			queryUpdateFunPimContact = new StringBuffer();
+			queryUpdateFunPimContact.append(SQL_UPDATE_FNBL_PIM_CONTACT_BEGIN
+					+ SQL_FIELD_LAST_UPDATE + SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact.append(SQL_FIELD_FIRST_NAME
+					+ SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact.append(SQL_FIELD_LAST_NAME
+					+ SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact.append(SQL_FIELD_NAME
+					+ SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact.append(SQL_FIELD_BIRTHDAY
+					+ SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact.append(SQL_FIELD_CATEGORY
+					+ SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact
+					.append(SQL_FIELD_STATUS + SQL_EQUALS_QUESTIONMARK
+							+ SQL_UPDATE_FNBL_PIM_CONTACT_END);
+
+			ps = con.prepareStatement(queryUpdateFunPimContact.toString());
+
+			ps.setLong(1, lastUpdate.getTime()); // last_update
+			ps.setString(2, firstName); // given_names
+			ps.setString(3, lastName); // family_names
+			ps.setString(4, fullName.toString()); // names_ordered
+			ps.setDate(5, anniversary); // birthdate
+			ps.setString(6, category); // category
+			ps.setString(7, String.valueOf(Def.PIM_STATE_UPDATED)); // last_status
+			ps.setLong(8, id_contact); // id_contact
+			ps.setLong(9, Long.parseLong(userId)); // id_owner
+			ps.executeUpdate();
+
+			// emails
+
+			// Apaga os emails do contato na tabela phpgw_cc_connections
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_connections WHERE id_connection IN (SELECT id_connection FROM phpgw_cc_contact_conns WHERE id_contact = ? AND id_typeof_contact_connection = 1)");
+			ps.setLong(1, id_contact);
+			ps.executeUpdate();
+
+			// Apaga as referencias de emails do contato na tabela
+			// phpgw_cc_contact_conns
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_conns WHERE id_contact = ? AND id_typeof_contact_connection = 1");
+			ps.setLong(1, id_contact);
+			ps.executeUpdate();
+
+			if (!emails.isEmpty()) {
+
+				boolean type_mail = true;
+
+				for (int i = 0, l = emails.size(); i < l; i++) {
+
+					email = emails.get(i);
+
+					if ((FIELD_EMAIL_1_ADDRESS).equals(email.getEmailType())) {
+						type = TYPE_EMAIL_1_ADDRESS;
+						type_mail = true;
+					} else if ((FIELD_EMAIL_2_ADDRESS).equals(email
+							.getEmailType())) {
+						type = TYPE_EMAIL_2_ADDRESS;
+						type_mail = false;
+					} else {
+						continue; // no save unknown property
+					}
+
+					String emailValue = email.getPropertyValueAsString();
+					emailValue = truncate(emailValue, SQL_EMAIL_DIM);
+
+					if (emailValue != null && emailValue.length() != 0) {
+
+						// Verifica o id maximo da tabela phpgw_cc_connections
+						ps = con.prepareStatement(SQL_ID_MAX_CONNECTIONS);
+						rs = ps.executeQuery();
+
+						if (rs.next()) {
+							long id = rs.getInt(1);
+							id_connection = (id == 0) ? 1 : ++id;
+						}
+
+						// Insere os emails do contato na tabela
+						// phpgw_cc_connections
+						ps = con.prepareStatement(SQL_INSERT_INTO_CONTACT_ITEM);
+						ps.setLong(1, id_connection);
+						ps.setString(2, type);
+						ps.setString(3, emailValue);
+						ps.setBoolean(4, type_mail);
+						ps.executeUpdate();
+
+						// Insere as referencias de emails do contato na tabela
+						// phpgw_cc_connections
+						ps = con
+								.prepareStatement(SQL_INSERT_INTO_FNBL_CONNECTIONS);
+						ps.setLong(1, id_contact);
+						ps.setLong(2, id_connection);
+						ps.setLong(3, 1);
+						ps.executeUpdate();
+					}
+				}
+			}
+
+			// phones
+
+			// Apaga os telefones do contato na tabela phpgw_cc_connections
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_connections WHERE id_connection IN (SELECT id_connection FROM phpgw_cc_contact_conns WHERE id_contact = ? AND id_typeof_contact_connection = 2)");
+			ps.setLong(1, id_contact);
+			ps.executeUpdate();
+
+			// Apaga as referencias de telefones do contato na tabela
+			// phpgw_cc_contact_conns
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_conns WHERE id_contact = ? AND id_typeof_contact_connection = 2");
+			ps.setLong(1, id_contact);
+			ps.executeUpdate();
+
+			if (!phones.isEmpty()) {
+
+				boolean type_fone = true; // Seta o primeiro telefone como
+				// principal no expresso
+
+				for (int i = 0, l = phones.size(); i < l; i++) {
+
+					phone = phones.get(i);
+					phoneType = phone.getPhoneType();
+
+					if ((FIELD_PRIMARY_TELEPHONE_NUMBER).equals(phoneType)) {
+						type = TYPE_PRIMARY_TELEPHONE_NUMBER;
+					} else if ((FIELD_HOME_TELEPHONE_NUMBER).equals(phoneType)) {
+						type = TYPE_HOME_TELEPHONE_NUMBER;
+					} else if ((FIELD_MOBILE_TELEPHONE_NUMBER)
+							.equals(phoneType)) {
+						type = TYPE_MOBILE_TELEPHONE_NUMBER;
+					} else if ((FIELD_BUSINESS_TELEPHONE_NUMBER)
+							.equals(phoneType)) {
+						type = TYPE_BUSINESS_TELEPHONE_NUMBER;
+					} else if ((FIELD_BUSINESS_FAX_NUMBER).equals(phoneType)) {
+						type = TYPE_BUSINESS_FAX_NUMBER;
+					} else if ((FIELD_PAGER_NUMBER).equals(phoneType)) {
+						type = TYPE_PAGER_NUMBER;
+					} else {
+						continue; // Unknown property: saves nothing
+					}
+
+					String phoneValue = phone.getPropertyValueAsString();
+					phoneValue = truncate(phoneValue, SQL_EMAIL_DIM);
+
+					if (phoneValue != null && phoneValue.length() != 0) {
+
+						// Verifica o id maximo da tabela phpgw_cc_connections
+						ps = con.prepareStatement(SQL_ID_MAX_CONNECTIONS);
+						rs = ps.executeQuery();
+
+						if (rs.next()) {
+							long id = rs.getInt(1);
+							id_connection = (id == 0) ? 1 : ++id;
+						}
+
+						// Insere os telefones do contato na tabela
+						// phpgw_cc_connections
+						ps = con.prepareStatement(SQL_INSERT_INTO_CONTACT_ITEM);
+						ps.setLong(1, id_connection);
+						ps.setString(2, type);
+						ps.setString(3, phoneValue);
+						ps.setBoolean(4, type_fone);
+						ps.executeUpdate();
+
+						type_fone = false; // os demais telefones serao setados
+						// como nao padrao
+
+						// Insere as referencias de telefones do contato na
+						// tabela phpgw_cc_connections
+						ps = con
+								.prepareStatement(SQL_INSERT_INTO_FNBL_CONNECTIONS);
+						ps.setLong(1, id_contact);
+						ps.setLong(2, id_connection);
+						ps.setLong(3, 2);
+						ps.executeUpdate();
+					}
+				}
+			}
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error updating contact.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=> Update item with ID " + id_contact + "\n");
+		log.info("\n\n=> PIMContactDAO updateItem end\n");
+
+		return Long.toString(id_contact);
+	}
+
+	/**
+	 * Removes the contact with given UID and sets its last_update field,
+	 * provided it has the same userId as this DAO. The deletion is soft
+	 * (reversible).
+	 *
+	 * @param uid
+	 *            corresponds to the id field in the phpgw_cc_contact table
+	 * @throws PIMDBAccessException
+	 */
+	public void removeItem(String uid) throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start removeItem " + uid + "\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_addresses WHERE id_address IN (SELECT id_address FROM phpgw_cc_contact_addrs WHERE id_contact = ?)");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_addrs WHERE id_contact = ?");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_connections WHERE id_connection IN (SELECT id_connection FROM phpgw_cc_contact_conns WHERE id_contact = ?)");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_conns WHERE id_contact = ?");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact WHERE id_contact = ?");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.executeUpdate();
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+
+			e.printStackTrace();
+			throw new PIMDBAccessException("\nError deleting contact.\n", e);
+
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	/**
+	 * Removes a contact, provided it has the same userId as this DAO. The
+	 * deletion is soft (reversible).
+	 *
+	 * @param contact
+	 *            whence the UID and the last update Date are extracted
+	 * @throws PIMDBAccessException
+	 */
+	public void removeItem(ContactWrapper contact) throws PIMDBAccessException {
+
+		removeItem(contact.getId());
+	}
+
+	/**
+	 * Removes all contacts, provided it has the same userId as this DAO. The
+	 * deletion is soft (reversible).
+	 *
+	 * @throws PIMDBAccessException
+	 */
+	public void removeAllItems() throws PIMDBAccessException {
+
+		log.info("\n\n =>DAO start removeAllItems\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_addresses WHERE id_address IN (SELECT id_address FROM phpgw_cc_contact_addrs WHERE id_contact IN(SELECT id_contact FROM phpgw_cc_contact WHERE id_owner = ?))");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_addrs WHERE id_contact IN(SELECT id_contact FROM phpgw_cc_contact WHERE id_owner = ?)");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_connections WHERE id_connection IN (SELECT id_connection FROM phpgw_cc_contact_conns WHERE id_contact IN(SELECT id_contact FROM phpgw_cc_contact WHERE id_owner = ?))");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_conns WHERE id_contact IN(SELECT id_contact FROM phpgw_cc_contact WHERE id_owner = ?)");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact WHERE id_owner = ?");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+
+			throw new PIMDBAccessException("\n=> Error deleting contacts.\n", e);
+
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	/**
+	 * Retrieves the UID list of all contacts belonging to the user.
+	 *
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+	public List getAllItems() throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMContactDAO getAllItems begin\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		List contacts = new ArrayList();
+		ResultSet rs = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement(SQL_GET_CONTACT_ID_LIST_BY_USER
+					+ SQL_ORDER_BY_ID);
+			ps.setLong(1, Long.parseLong(userId));
+
+			rs = ps.executeQuery();
+
+			while (rs.next()) {
+				contacts.add(Long.toString(rs.getLong(1))); // It's the first
+				// and only column
+			}
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new PIMDBAccessException("\n=> Error listing contacts.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=> PIMContactDAO getAllItems end\n");
+
+		return contacts;
+	}
+
+	/**
+	 * Gets the contact with given UID, provided it has the same userId as this
+	 * DAO.
+	 *
+	 * @param uid
+	 *            corresponds to the id field in the fnbl_pim_contact table
+	 * @throws PIMDBAccessException
+	 * @return the contact as a ContactWrapper object.
+	 */
+	public ContactWrapper getItem(String uid) throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start getItem " + uid + "\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		ContactWrapper c;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement(SQL_GET_CONTACT_BY_ID_USER);
+			ps.setLong(1, Long.parseLong(uid));
+			ps.setLong(2, Long.parseLong(userId));
+			rs = ps.executeQuery();
+			c = createContact(uid, rs);
+
+			ps = con.prepareStatement(SQL_GET_CONTACT_ITEM_BY_ID);
+			ps.setLong(1, Long.parseLong(uid));
+			rs = ps.executeQuery();
+
+			try {
+				addPIMContactItems(c, rs);
+			} catch (SQLException sqle) {
+				throw new SQLException(
+						"\nError while adding extra PIM contact "
+								+ "information.\n" + sqle.getMessage(), sqle
+								.getSQLState());
+			}
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new PIMDBAccessException("\n=> Error seeking contact.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return c;
+	}
+
+	/**
+	 * Retrieves the UID list of the contacts considered to be "twins" of a
+	 * given contact.
+	 *
+	 * @param c
+	 *            the Contact object representing the contact whose twins need
+	 *            be found. In the present implementation, only the following
+	 *            data matter:
+	 *            <UL>
+	 *            <LI>first name
+	 *            <LI>last name
+	 *            </UL>
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects) that may be empty but not null
+	 */
+	public List getTwinItems(Contact c) throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMContactDAO getTwinItems begin\n");
+
+		List twins = new ArrayList();
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+
+		try {
+
+			String firstName = c.getName().getFirstName()
+					.getPropertyValueAsString();
+			String lastName = c.getName().getLastName()
+					.getPropertyValueAsString();
+
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			if (firstName == null || ("null".equals(firstName))) {
+				firstName = "";
+			}
+			if (lastName == null || ("null".equals(lastName))) {
+				lastName = "";
+			}
+
+			String fnSearch = (firstName.length() == 0) ? "<N/A>" : firstName;
+			String lnSearch = (lastName.length() == 0) ? "<N/A>" : lastName;
+			StringBuilder sb = new StringBuilder(100);
+
+			sb.append("\n\nLooking for items having: ").append(
+					"\n> first name   : '").append(fnSearch).append('\'')
+					.append("\n> last  name   : '").append(lnSearch).append(
+							'\'').append("\n");
+
+			log.info(sb.toString());
+
+			ps = con.prepareStatement(SQL_GET_CONTACT_TWIN_ID_LIST
+					+ SQL_ORDER_BY_ID);
+			ps.setLong(1, Long.parseLong(userId));
+			ps.setString(2, firstName);
+			ps.setString(3, firstName);
+			ps.setString(4, lastName);
+			ps.setString(5, lastName);
+			rs = ps.executeQuery();
+
+			long twinId;
+
+			while (rs.next()) {
+
+				twinId = rs.getLong(1); // The id is the first and only column
+				log.info("\n\n=> Twin found: " + twinId + "\n");
+				twins.add(Long.toString(twinId));
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error retrieving twin.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=> PIMContactDAO getTwinItems end\n");
+
+		return twins;
+	}
+
+	/**
+	 * Retrieves the state of the given item, provided it's been modified after
+	 * a certain moment.
+	 *
+	 * @param uid
+	 *            the UID of the item to be checked (as a String object)
+	 * @param since
+	 *            the Timestamp that the item's lastUpdate field is checked
+	 *            against: if the item has been modified before that moment, an
+	 *            "unchanged" state marker is returned
+	 * @throws PIMDBAccessException
+	 * @return a char identifying either one of the 3 standard states ("new",
+	 *         "deleted", "updated") or the special "unchanged" status, all of
+	 *         them as defined in com.funambol.pim.util.Def
+	 */
+	public char getItemState(String uid, Timestamp since)
+			throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start getItemState\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		char status;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement(SQL_GET_STATUS_BY_ID_USER_TIME);
+			ps.setLong(1, Long.parseLong(uid));
+			ps.setLong(2, Long.parseLong(userId));
+			ps.setLong(3, since.getTime());
+			rs = ps.executeQuery();
+
+			if (!rs.next()) {
+
+				status = Def.PIM_STATE_UNCHANGED;
+
+				log.info("\n\n=> Item " + uid + "'s status wasn't retrieved "
+						+ "because the item hasn't been modified since "
+						+ since + "\n");
+
+			} else {
+
+				status = rs.getString(1).charAt(0);
+
+				log.info("\n\n=> Item " + uid + " has status " + status + "\n");
+
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException(
+					"\n=> Error retrieving item state.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return status;
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	/**
+	 * Creates a ContactWrapper object from a ResultSet. Only the basic data are
+	 * set.
+	 *
+	 * @param wrapperId
+	 *            the UID of the wrapper object to be returned
+	 * @param rs
+	 *            the result of the execution of a proper SQL SELECT statement
+	 *            on the phpgw_cc_contact table, with the cursor before its
+	 *            first row
+	 * @return a newly created ContactWrapper initialized with the fields in the
+	 *         result set
+	 * @throws java.sql.SQLException
+	 * @throws NotFoundException
+	 */
+	private static ContactWrapper createContact(String wrapperId, ResultSet rs)
+			throws SQLException, NotFoundException {
+
+		if (!rs.next()) {
+			throw new NotFoundException("\n\n=> No contact found.\n");
+		}
+
+		ResultSetMetaData rsmd = rs.getMetaData();
+		ContactWrapper cw = null;
+
+		String column = null;
+		String uid = null;
+		String userId = null;
+
+		uid = String.valueOf(rs.getLong(SQL_FIELD_ID));
+		userId = rs.getString(SQL_FIELD_USERID);
+
+		Contact c = new Contact();
+		cw = new ContactWrapper(wrapperId, userId, c);
+
+		int columnCount = rsmd.getColumnCount();
+
+		for (int i = 1; i <= columnCount; ++i) {
+
+			column = rsmd.getColumnName(i);
+
+			// General
+			if (SQL_FIELD_ID.equalsIgnoreCase(column)) {
+				// Does nothing: field already set at construction time
+			} else if (SQL_FIELD_LAST_UPDATE.equalsIgnoreCase(column)) {
+				cw.setLastUpdate(new Timestamp(rs.getLong(i)));
+			} else if (SQL_FIELD_USERID.equalsIgnoreCase(column)) {
+				// Does nothing: field already set at construction time
+			} else if (SQL_FIELD_STATUS.equalsIgnoreCase(column)) {
+				cw.setStatus(rs.getString(i).charAt(0));
+			} else if (SQL_FIELD_FIRST_NAME.equalsIgnoreCase(column)) {
+				c.getName().getFirstName().setPropertyValue(rs.getString(i));
+			} else if (SQL_FIELD_LAST_NAME.equalsIgnoreCase(column)) {
+				c.getName().getLastName().setPropertyValue(rs.getString(i));
+			} else if (SQL_FIELD_BIRTHDAY.equalsIgnoreCase(column)) {
+				c.getPersonalDetail().setBirthday(rs.getString(i));
+			} else if (SQL_FIELD_CATEGORY.equalsIgnoreCase(column)) {
+				c.getCategories().setPropertyValue(rs.getString(i));
+			} else {
+				throw new SQLException("\n=> Unhandled column: " + column);
+			}
+		}
+
+		return cw;
+	}
+
+	/**
+	 * Attaches extra information to a contact on the basis of a ResultSet.
+	 *
+	 * @param c
+	 *            the contact still lacking extra information
+	 * @param rs
+	 *            the result of the execution of a proper SQL SELECT statement
+	 *            on the phpgw_cc_connections and phpgw_cc_contact_conns table,
+	 *            with the cursor before its first row
+	 * @return the ContactWrapper object with extra information attached
+	 * @throws java.sql.SQLException
+	 */
+	private static ContactWrapper addPIMContactItems(ContactWrapper cw,
+			ResultSet rs) throws SQLException {
+
+		ResultSetMetaData rsmd = rs.getMetaData();
+		Contact c = cw.getContact();
+
+		String type = null;
+		String value = null;
+		String name = null;
+		String column = null;
+
+		int columnCount = 0;
+
+		Phone phone;
+		Email email;
+
+		while (rs.next()) {
+
+			columnCount = rsmd.getColumnCount();
+
+			for (int i = 1; i <= columnCount; ++i) {
+
+				column = rsmd.getColumnName(i);
+
+				if (SQL_FIELD_ITEM_NAME.equalsIgnoreCase(column)) {
+					name = rs.getString(i);
+				} else if (SQL_FIELD_ITEM_VALUE.equalsIgnoreCase(column)) {
+					value = rs.getString(i);
+				} else if (SQL_FIELD_ITEM_TYPE.equalsIgnoreCase(column)) {
+					type = rs.getString(i);
+				} else {
+					throw new SQLException("\n=> Unhandled column: " + column);
+				}
+			}
+
+			if (type.equals("1")) { // email
+
+				if (TYPE_EMAIL_1_ADDRESS.equalsIgnoreCase(name)) {
+					email = new Email();
+					email.setEmailType(FIELD_EMAIL_1_ADDRESS);
+					email.setPropertyValue(value);
+					c.getPersonalDetail().addEmail(email);
+				} else if (TYPE_EMAIL_2_ADDRESS.equalsIgnoreCase(name)) {
+					email = new Email();
+					email.setEmailType(FIELD_EMAIL_2_ADDRESS);
+					email.setPropertyValue(value);
+					c.getPersonalDetail().addEmail(email);
+				}
+			}
+
+			if (type.equals("2")) { // telefone
+
+				if (TYPE_PRIMARY_TELEPHONE_NUMBER.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_PRIMARY_TELEPHONE_NUMBER);
+					phone.setPropertyValue(value);
+					c.getBusinessDetail().addPhone(phone);
+				} else if (TYPE_HOME_TELEPHONE_NUMBER.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_HOME_TELEPHONE_NUMBER);
+					phone.setPropertyValue(value);
+					c.getPersonalDetail().addPhone(phone);
+				} else if (TYPE_MOBILE_TELEPHONE_NUMBER.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_MOBILE_TELEPHONE_NUMBER);
+					phone.setPropertyValue(value);
+					c.getPersonalDetail().addPhone(phone);
+				} else if (TYPE_BUSINESS_TELEPHONE_NUMBER
+						.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_BUSINESS_TELEPHONE_NUMBER);
+					phone.setPropertyValue(value);
+					c.getBusinessDetail().addPhone(phone);
+				} else if (TYPE_BUSINESS_FAX_NUMBER.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_BUSINESS_FAX_NUMBER);
+					phone.setPropertyValue(value);
+					c.getBusinessDetail().addPhone(phone);
+				} else if (TYPE_PAGER_NUMBER.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_PAGER_NUMBER);
+					phone.setPropertyValue(value);
+					c.getBusinessDetail().addPhone(phone);
+				}
+			}
+		}
+
+		return cw;
+	}
+
+	/**
+	 * Retrieves the UID list of the contacts belonging to the user filtered
+	 * according to the given time interval and status.
+	 *
+	 * @param since
+	 *            the earliest allowed last-update Timestamp
+	 * @param to
+	 *            the latest allowed last-update Timestamp
+	 * @param status
+	 *            'D' for deleted items, 'N' for new items, 'U' for updated
+	 *            items
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+// Alteracao - Incluido parametro sourceURI - emerson-faria.nobre@serpro.gov.br - 17/set/2009
+	protected List getItemsHavingStatus(Timestamp since, Timestamp to,
+			char status, String sourceURI) throws PIMDBAccessException {
+
+		log.info("\n\n=> Seeking '" + status + "' items of sourceURI:'" + sourceURI + "' in time interval (" + since + "; " + to + ")\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		List contacts = new ArrayList();
+		ResultSet rs = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			if (status == 'D') {
+//Correcao - Incluido typecast 'id_contact::text' para compatibilizar com o postgresql 8.3 - emerson-faria.nobre@serpro.gov.br - 26/feb/2010
+//				ps = con.prepareStatement("SELECT guid FROM fnbl_client_mapping WHERE sync_source = '" + sourceURI + "' AND guid NOT IN (SELECT id_contact FROM phpgw_cc_contact WHERE id_owner = ?) AND principal = ? ORDER BY guid");
+				ps = con.prepareStatement("SELECT guid FROM fnbl_client_mapping WHERE sync_source = '" + sourceURI + "' AND guid NOT IN (SELECT id_contact::text FROM phpgw_cc_contact WHERE id_owner = ?) AND principal = ? ORDER BY guid");
+
+                ps.setLong(1, Long.parseLong(userId));
+				ps.setLong(2, this.principal.getId());
+				rs = ps.executeQuery();
+
+			} else {
+
+				ps = con
+						.prepareStatement(SQL_GET_FNBL_PIM_CONTACT_ID_LIST_BY_USER_TIME_STATUS
+								+ SQL_ORDER_BY_ID);
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setLong(2, since.getTime());
+				ps.setLong(3, to.getTime());
+				ps.setString(4, String.valueOf(status));
+				rs = ps.executeQuery();
+			}
+
+			while (rs.next()) {
+				contacts.add(Long.toString(rs.getLong(1))); // It's the first
+				// and only column
+				log.info("\n\n=> Item found " + rs.getLong(1) + "\n");
+			}
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new PIMDBAccessException("\n=> Error listing contacts.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return contacts;
+	}
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMCalendarDAO.java.original
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMCalendarDAO.java.original	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMCalendarDAO.java.original	(revision 2082)
@@ -0,0 +1,1439 @@
+/**
+ * This class implements methods to access contacts data in domino server data
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.items.dao;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import br.com.prognus.psync.exception.PIMDBAccessException;
+import br.com.prognus.psync.items.model.CalendarWrapper;
+import br.com.prognus.psync.util.Def;
+
+import com.funambol.common.pim.calendar.Calendar;
+import com.funambol.common.pim.calendar.CalendarContent;
+import com.funambol.common.pim.calendar.Event;
+import com.funambol.common.pim.calendar.ExceptionToRecurrenceRule;
+import com.funambol.common.pim.calendar.RecurrencePattern;
+import com.funambol.common.pim.calendar.Reminder;
+import com.funambol.common.pim.utility.TimeUtils;
+import com.funambol.framework.security.Sync4jPrincipal;
+import com.funambol.framework.server.store.NotFoundException;
+import com.funambol.framework.tools.DBTools;
+
+public class PIMCalendarDAO extends PIMEntityDAO {
+
+	// --------------------------------------------------------------- Constants
+
+	private static final String SQL_AND_NO_SUBJECT_IS_SET = "AND ((C.title IS null) OR (C.title = '')) ";
+
+	private static final String SQL_AND_SUBJECT_EQUALS_QUESTIONMARK = "AND C.title = ? ";
+
+	private static final String SQL_AND_NO_dtstart_IS_SET = "AND C.datetime IS null ";
+
+	private static final String SQL_AND_dtstart_EQUALS_QUESTIONMARK = "AND C.datetime = ? ";
+
+	private static final String SQL_AND_NO_DEND_IS_SET = "AND C.edatetime IS null ";
+
+	private static final String SQL_AND_DEND_EQUALS_QUESTIONMARK = "AND C.edatetime = ? ";
+
+	private static final String SQL_EQUALS_QUESTIONMARK = " = ?";
+
+	private static final String SQL_EQUALS_QUESTIONMARK_COMMA = " = ?, ";
+
+	protected static final int SQL_BODY_DIM = 4096;
+
+	protected static final int SQL_LOCATION_DIM = 255;
+
+	protected static final int SQL_SUBJECT_DIM = 255;
+
+	protected static final String SQL_FIELD_ID = "cal_id";
+
+	protected static final String SQL_FIELD_USERID = "owner";
+
+	protected static final String SQL_FIELD_LAST_UPDATE = "last_update";
+
+	protected static final String SQL_FIELD_STATUS = "last_status";
+
+	protected static final String SQL_FIELD_CAL_TYPE = "cal_type";
+
+	protected static final String SQL_FIELD_SENSITIVITY = "is_public";
+
+	protected static final String SQL_FIELD_SUBJECT = "title";
+
+	protected static final String SQL_FIELD_BODY = "description";
+
+	protected static final String SQL_FIELD_LOCATION = "location";
+
+	protected static final String SQL_FIELD_DATE_START = "datetime";
+
+	protected static final String SQL_FIELD_CATEGORY = "category";
+
+	protected static final String SQL_FIELD_DATE_END = "edatetime";
+
+	protected static final String SQL_FIELD_DATE_NOW = "mdatetime";
+
+	private static final String SQL_ORDER_BY_ID = "ORDER BY cal_id ASC";
+
+	private static final String SQL_INSERT_INTO_EXP_PIM_CALENDAR = "INSERT INTO phpgw_cal "
+			+ "(uid, owner, category, datetime, mdatetime, edatetime, cal_type, is_public, title, description, location, ex_participants, last_update, last_status) "
+			+ "VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+	private static final String SQL_INSERT_INTO_EXP_PIM_CALENDAR_USER = "INSERT INTO phpgw_cal_user "
+			+ "(cal_id, cal_login) " + "VALUES " + "(?, ?)";
+
+	private static final String SQL_INSERT_INTO_EXP_PIM_CALENDAR_REPEATS = "INSERT INTO phpgw_cal_repeats "
+			+ "(cal_id, recur_type, recur_use_end, recur_enddate, recur_interval, recur_data, recur_exception) "
+			+ "VALUES " + "(?, ?, ?, ?, ?, ?, ?)";
+
+	private static final String SQL_INSERT_INTO_EXP_PIM_ALARM = "INSERT INTO phpgw_async "
+			+ "(id, next, times, method, data, account_id) "
+			+ "VALUES "
+			+ "(?, ?, ?, ?, ?, ?)";
+
+	private static final String SQL_GET_FNBL_PIM_CALENDAR_EXCEPTION_BY_CALENDAR = "SELECT recur_type, recur_use_end, recur_enddate, recur_interval, recur_data, recur_exception, datetime FROM phpgw_cal_repeats AS R LEFT JOIN  phpgw_cal AS C ON R.cal_id = C.cal_id WHERE C.cal_id = ?";
+
+	private static final String SQL_GET_EXP_PIM_CALENDAR_ID_LIST_BY_USER_TIME_STATUS = "SELECT C.cal_id FROM phpgw_cal_user as U, phpgw_cal as C WHERE C.cal_id = U.cal_id AND U.cal_login = ? AND C.last_update > ? AND C.last_update < ? AND C.last_status = ? AND U.cal_status != 'R' ";
+
+	private static final String SQL_GET_EXP_PIM_CALENDAR_ID_LIST_BY_USER = "SELECT U.cal_id FROM phpgw_cal_user as U, phpgw_cal as C WHERE C.cal_id = U.cal_id AND U.cal_login = ? AND U.cal_status != 'R' ";
+
+	private static final String SQL_GET_STATUS_BY_ID_USER_TIME = "SELECT last_status FROM phpgw_cal WHERE cal_id = ? AND owner = ? AND last_update > ? LIMIT 1";
+
+	private static final String SQL_GET_EXP_PIM_CALENDAR_BY_ID_USERID = "SELECT cal_id, last_update, owner, last_status, location, title, description, datetime, edatetime, is_public, category FROM phpgw_cal WHERE cal_id = ?";
+
+	private static final String SQL_UPDATE_EXP_PIM_CALENDAR_BEGIN = "UPDATE phpgw_cal SET ";
+
+	private static final String SQL_UPDATE_EXP_PIM_CALENDAR_END = " WHERE cal_id = ? AND owner = ? ";
+
+	private Sync4jPrincipal principal = null;
+
+	/*
+	 * @see PIMEntityDAO#PIMEntityDAO(String, String)
+	 */
+	public PIMCalendarDAO(String jndiDataSourceName, Sync4jPrincipal principal) {
+
+		super(jndiDataSourceName, principal.getUsername());
+		log.info("\n\n=> Created new PIMCalendarDAO for data source " + jndiDataSourceName + " and user ID " + userId);
+		this.principal = principal;
+	}
+
+	// ----------------------------------------------------------- Public
+	// methods
+
+	/**
+	 * Adds a calendar. If necessary, a new ID is generated and set in the
+	 * CalendarWrapper.
+	 *
+	 * @param c
+	 *            as a CalendarWrapper object, usually without an ID set.
+	 * @throws PIMDBAccessException
+	 *
+	 * @see CalendarWrapper
+	 */
+	public void addItem(CalendarWrapper cw) throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMCalendarDAO addItem begin");
+
+		Connection 		  con 		= null;
+		PreparedStatement ps 		= null;
+		RecurrencePattern rp 		= null;
+		ResultSet 		  rs 		= null;
+		Reminder          reminder  = null;
+
+		String 	subject 	 	= null;
+		String 	body 		 	= null;
+		String 	location 	 	= null;
+		String 	is_public 	 	= null;
+		String 	category     	= null;
+
+		Date dtstart = null;
+		Date dend    = null;
+		long id_cal  = 0;
+
+		try {
+
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			CalendarContent c = cw.getCalendar().getCalendarContent();
+			Timestamp lastUpdate = cw.getLastUpdate();
+
+			lastUpdate = (lastUpdate == null) ? new Timestamp(System.currentTimeMillis()) : lastUpdate;
+
+			// Recebe a data inicial do evento
+			String sd = null;
+			if (c.getDtStart() != null) {
+				sd = c.getDtStart().getPropertyValueAsString(); // data inicial no formato YYYYMMddTHHMMSSZ
+				dtstart = getDateFromString(sd); // data inicial no formato YYYY-MM-dd
+			}
+
+			// Recebe a data final do evento
+			String ed = null;
+			if (c.getDtEnd() != null) {
+				ed = c.getDtEnd().getPropertyValueAsString();
+				dend = getDateFromString(ed);
+			}
+
+			rp          = c.getRecurrencePattern(); // Recebe a repetiÃ§Ã£o do alarme
+			reminder    = c.getReminder(); // Recebe o alarme do evento
+			body 		= stringFrom(c.getDescription()); // Descricao completa do evento
+			location 	= stringFrom(c.getLocation()); // Localizacao do evento
+			is_public 	= (stringFrom(c.getAccessClass())).equals("2") ? "0" : "1"; // Evento particular ou publico
+			subject 	= stringFrom(c.getSummary()); // Titulo do evento
+			category    = stringFrom(c.getCategories());
+
+			// Verifica o parametro uid necessario para o expresso
+			ps = con.prepareStatement("select config_value from phpgw_config WHERE config_name = 'hostname' AND config_app = 'phpgwapi'");
+			rs = ps.executeQuery();
+			String uid = rs.next() ? ("-@" + rs.getString(1)) : "";
+
+			// Verifica a categoria do evento
+			if(category != null && (!category.equals(""))){
+				category = addCategory(category);
+			}
+
+			// Prepara os dados para inserir o evento no banco de dados
+			ps = con.prepareStatement(SQL_INSERT_INTO_EXP_PIM_CALENDAR);
+			ps.setString(1, uid); // -@hostname
+			ps.setLong(2, Long.parseLong(userId)); // owner
+			ps.setString(3, category); // category
+			ps.setLong(4, new Long(Long.toString(dtstart.getTime()).substring(0, 10))); // datetime
+			ps.setLong(5, new Long(Long.toString(lastUpdate.getTime()).substring(0, 10))); // mdatetime
+			ps.setLong(6, new Long(Long.toString(dend.getTime()).substring(0, 10))); // edatetime
+			ps.setString(7, (rp == null) ? "E" : "M"); // cal_type
+			ps.setLong(8, Long.parseLong(is_public)); // is_plublic
+			ps.setString(9, truncate(subject, SQL_SUBJECT_DIM)); // title
+			ps.setString(10, truncate(body, SQL_BODY_DIM)); // body of event, a little description of it
+			ps.setString(11, truncate(location, SQL_LOCATION_DIM)); // location of the event
+			ps.setString(12, ""); // Participants outside
+			ps.setLong(13, lastUpdate.getTime()); // last update
+			ps.setString(14, String.valueOf(Def.PIM_STATE_NEW)); // status
+			ps.executeUpdate();
+
+			// Verifica o id maximo do evento inserido
+			ps = con.prepareStatement("select max(cal_id) from phpgw_cal limit 1");
+			rs = ps.executeQuery();
+
+			if (rs.next()) {
+				long id = rs.getInt(1);
+				id_cal = (id == 0) ? 1 : id;
+			}
+
+			cw.setId(Long.toString(id_cal));
+
+			ps = con.prepareStatement(SQL_INSERT_INTO_EXP_PIM_CALENDAR_USER);
+			ps.setLong(1, id_cal);
+			ps.setLong(2, Long.parseLong(userId)); // owner
+			ps.executeUpdate();
+
+			// Evento com repetiÃ§Ã£o
+			if (rp != null) {
+				addRecurrencePattern(rp, id_cal, sd);
+			}
+
+			// Evento com Alarme
+			if (reminder != null && reminder.isActive()) {
+				addReminder(reminder, id_cal);
+			}
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error adding a calendar item: "
+					+ e.getMessage());
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=>Added item with ID " + id_cal);
+		log.info("\n\n=>PIMCalendarDAO addItem end");
+	}
+
+	/**
+	 * Updates a calendar.
+	 *
+	 * @param c
+	 *            as a CalendarWrapper object. If its last update time is null,
+	 *            then it's set to the current time.
+	 * @return the UID of the contact
+	 * @throws PIMDBAccessException
+	 *
+	 * @see CalendarWrapper
+	 */
+	public String updateItem(CalendarWrapper cw) throws PIMDBAccessException, Exception {
+
+		log.info("\n\n=> PIMCalendarDAO updateItem begin");
+
+		Connection 			con 	 = null;
+		PreparedStatement 	ps 		 = null;
+		ResultSet 			rs 		 = null;
+		CalendarContent 	c 		 = null;
+		RecurrencePattern 	rp 	 	 = null;
+		Reminder 			reminder = null;
+
+		long id_cal = 0;
+		long dt1    = 0;
+		long dt2    = 0;
+
+		Date dend 	 = null;
+		Date dtstart = null;
+
+		String body 	 = null;
+		String location  = null;
+		String subject   = null;
+		String is_public = null;
+		String category  = null;
+
+		StringBuffer queryUpdateFunPimCalendar = null;
+
+		try {
+
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			Timestamp lastUpdate = (cw.getLastUpdate() == null) ? new Timestamp(System.currentTimeMillis()) : cw.getLastUpdate();
+
+			c = cw.getCalendar().getCalendarContent();
+
+			id_cal = Long.parseLong(cw.getId());
+
+			// Verifica se o evento pertence ao dono para ser atualizado
+			ps = con.prepareStatement("SELECT cal_id FROM phpgw_cal WHERE cal_id = ? AND owner = ?");
+			ps.setLong(1, id_cal);
+			ps.setLong(2, Long.parseLong(userId));
+			rs = ps.executeQuery();
+
+			if (rs.next()) {
+
+				rp 			= c.getRecurrencePattern(); // Recebe a repetiÃ§Ã£o do alarme
+				reminder    = c.getReminder(); // Recebe o alarme do evento
+				body 		= stringFrom(c.getDescription()); // DescriÃ§Ã£o completa do evento
+				location 	= stringFrom(c.getLocation()); // Localizacao do evento
+				is_public 	= (stringFrom(c.getAccessClass())).equals("2") ? "0" : "1"; // Evento particular ou publico
+				subject 	= stringFrom(c.getSummary()); // Titulo do evento
+				category    = stringFrom(c.getCategories());
+
+				// Verifica a categoria do evento
+				if(category != null && (!category.equals(""))){
+					category = addCategory(category);
+				} else {
+					category = null;
+				}
+
+				// Recebe a data inicial do evento
+				String sd = null;
+				if (c.getDtStart() != null) {
+					sd = c.getDtStart().getPropertyValueAsString();
+					dtstart = getDateFromString(sd);
+					dt1 = new Long(Long.toString(dtstart.getTime()).substring(0, 10));
+				}
+
+				// Recebe a data final do evento
+				String ed = null;
+				if (c.getDtEnd() != null) {
+					ed = c.getDtEnd().getPropertyValueAsString();
+					dend = getDateFromString(ed);
+					dt2 = new Long(Long.toString(dend.getTime()).substring(0, 10));
+				}
+
+				// Prepara os dados para inserir o evento no banco de dados
+				queryUpdateFunPimCalendar = new StringBuffer();
+				queryUpdateFunPimCalendar.append(SQL_UPDATE_EXP_PIM_CALENDAR_BEGIN);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_CATEGORY).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_DATE_START).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_DATE_NOW).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_DATE_END).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_CAL_TYPE).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_SENSITIVITY).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_SUBJECT).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_BODY).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_LOCATION).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_LAST_UPDATE).append(SQL_EQUALS_QUESTIONMARK_COMMA);
+				queryUpdateFunPimCalendar.append(SQL_FIELD_STATUS).append(SQL_EQUALS_QUESTIONMARK);
+				queryUpdateFunPimCalendar.append(SQL_UPDATE_EXP_PIM_CALENDAR_END);
+
+				ps = con.prepareStatement(queryUpdateFunPimCalendar.toString());
+				ps.setString(1, category); //category
+				ps.setLong(2, dt1); // datetime
+				ps.setLong(3, new Long(Long.toString(lastUpdate.getTime()).substring(0, 10))); // mdatetime
+				ps.setLong(4, dt2); // datetime
+				ps.setString(5, (rp == null) ? "E" : "M"); // cal_type
+				ps.setLong(6, Long.parseLong(is_public)); // is_plublic
+				ps.setString(7, truncate(subject, SQL_SUBJECT_DIM));
+				ps.setString(8, truncate(body, SQL_BODY_DIM));
+				ps.setString(9, truncate(location, SQL_LOCATION_DIM)); // location
+				ps.setLong(10, lastUpdate.getTime()); // last update
+				ps.setString(11, String.valueOf(Def.PIM_STATE_UPDATED)); // status
+				ps.setLong(12, id_cal); // cal_id
+				ps.setString(13, userId); // owner
+				ps.executeUpdate();
+
+				// Evento com repetiÃ§Ã£o
+				if (rp != null) {
+					addRecurrencePattern(rp, id_cal, sd); // adiciona a repeticao
+				} else {
+					deleteRecurrencePattern(id_cal); // delete a repeticao
+				}
+
+				// Evento com Alarme
+				if (reminder != null && reminder.isActive() && reminder.getTime() != null) {
+					addReminder(reminder, id_cal); // adiciona o alarme
+				} else {
+					deleteReminder(id_cal); // deleta o alarme
+				}
+			}
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+
+			throw new PIMDBAccessException(
+					"\n=> Error updating a calendar item: " + e.getMessage());
+
+		} finally {
+
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=>Update item with ID " + id_cal);
+		log.info("\n\n=>PIMCalendarDAO updateItem end");
+
+		return Long.toString(id_cal);
+	}
+
+	/**
+	 * Retrieves the UID list of all calendars belonging to the user of the type
+	 * prescribed by the DAO set-up.
+	 *
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+	public List getAllItems() throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMCalendarDAO getAllItems begin");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		List calendars = new ArrayList();
+		ResultSet rs = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement(SQL_GET_EXP_PIM_CALENDAR_ID_LIST_BY_USER + SQL_ORDER_BY_ID);
+			ps.setString(1, userId);
+
+			rs = ps.executeQuery();
+
+			while (rs.next()) {
+				calendars.add(Long.toString(rs.getLong(1))); // It's the first
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error listing contacts. " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=> PIMCalendarDAO getAllItems end");
+
+		return calendars;
+	}
+
+	public CalendarWrapper getItem(String uid) throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start getItem " + uid);
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		ResultSet rs1 = null;
+		CalendarWrapper cw = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			try {
+
+				ps = con.prepareStatement(SQL_GET_EXP_PIM_CALENDAR_BY_ID_USERID);
+				ps.setLong(1, Long.parseLong(uid));
+				rs = ps.executeQuery();
+
+				ps = con.prepareStatement("SELECT CT.cat_name FROM phpgw_cal as C, phpgw_categories as CT WHERE C.category = CT.cat_id AND C.cal_id = ?");
+				ps.setLong(1, Long.parseLong(uid));
+				rs1 = ps.executeQuery();
+
+				cw = createCalendar(uid, userId, rs, (rs1.next() ? rs1.getString(1) : ""));
+
+			} catch (SQLException sqle) {
+				throw new SQLException("\n=> Error while adding createCalendar. " + sqle, sqle.getSQLState());
+			}
+
+			try {
+
+				ps = con.prepareStatement(SQL_GET_FNBL_PIM_CALENDAR_EXCEPTION_BY_CALENDAR);
+				ps.setLong(1, Long.parseLong(uid));
+				rs = ps.executeQuery();
+				cw = addPIMCalendarExceptions(cw, rs);
+
+			} catch (SQLException sqle) {
+				throw new SQLException("\n=> Error while adding addPIMCalendarExceptions. " + sqle, sqle.getSQLState());
+			}
+
+			try {
+
+				ps = con.prepareStatement("SELECT next, times, data from phpgw_async WHERE id = ? AND account_id = ?");
+				ps.setString(1, "cal:" + uid + ":0");
+				ps.setLong(2, Long.parseLong(userId));
+				rs = ps.executeQuery();
+				cw = addPIMCalendarAlarm(cw, rs);
+
+			} catch (SQLException sqle) {
+				throw new SQLException("\n=> Error while adding addPIMCalendarAlarm. " + sqle, sqle.getSQLState());
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error retrieving a calendar item: " + e, e);
+		} finally {
+			DBTools.close(con, ps, rs);
+			DBTools.close(con, ps, rs1);
+		}
+
+		return cw;
+	}
+
+	/**
+	 * Removes the calendar with given UID and sets its last_update field,
+	 * provided it has the same userId as this DAO. The deletion is soft
+	 * (reversible).
+	 *
+	 * @param uid
+	 *            corresponds to the id field in the phpgw_cal table
+	 * @throws PIMDBAccessException
+	 */
+	public void removeItem(String uid) throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start removeItem " + uid);
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			ps = con.prepareStatement("SELECT cal_id FROM phpgw_cal WHERE cal_id = ? AND owner = ?");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.setLong(2, Long.parseLong(userId));
+			rs = ps.executeQuery();
+
+			if (rs.next()) {
+
+				ps = con.prepareStatement("DELETE FROM phpgw_async WHERE id = ?");
+				ps.setString(1, "cal:" + uid + ":0");
+				ps.executeUpdate();
+
+				ps = con.prepareStatement("DELETE FROM phpgw_cal_repeats WHERE cal_id = ?");
+				ps.setLong(1, Long.parseLong(uid));
+				ps.executeUpdate();
+
+				ps = con.prepareStatement("DELETE FROM phpgw_cal_user WHERE cal_id = ?");
+				ps.setLong(1, Long.parseLong(uid));
+				ps.executeUpdate();
+
+				ps = con.prepareStatement("DELETE FROM phpgw_cal WHERE cal_id = ?");
+				ps.setLong(1, Long.parseLong(uid));
+				ps.executeUpdate();
+
+			} else {
+
+				ps = con.prepareStatement("UPDATE phpgw_cal_user SET cal_status = 'R' WHERE cal_id = ? AND cal_login = ?");
+				ps.setLong(1, Long.parseLong(uid));
+				ps.setLong(2, Long.parseLong(userId));
+				ps.executeUpdate();
+
+			}
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error deleting calendar.", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+	}
+
+	/**
+	 * Removes a calendar, provided it has the same userId as this DAO. The
+	 * deletion is soft (reversible).
+	 *
+	 * @param calendar
+	 *            whence the UID and the last update Date are extracted
+	 * @throws PIMDBAccessException
+	 */
+	public void removeItem(CalendarWrapper calendar) throws PIMDBAccessException {
+		removeItem(calendar.getId());
+	}
+
+	/**
+	 * Deletes (reversibly) all calendars belonging to the user of the type
+	 * prescribed by the DAO set-up. The last_update field of the (soft-)deleted
+	 * items will be set at a given timestamp.
+	 *
+	 * @throws PIMDBAccessException
+	 */
+	public void removeAllItems() throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start removeAllItems");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			ps = con.prepareStatement("DELETE FROM phpgw_async WHERE account_id = ?");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con.prepareStatement("DELETE FROM phpgw_cal_repeats WHERE cal_id IN(SELECT cal_id FROM phpgw_cal_user WHERE cal_login = ?)");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con.prepareStatement("DELETE FROM phpgw_cal_user WHERE cal_id IN(SELECT cal_id FROM phpgw_cal WHERE owner = ?)");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con.prepareStatement("DELETE FROM phpgw_cal WHERE owner = ?");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error deleting calendars.", e);
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	/**
+	 * Retrieves the UID list of the calendars belonging to the user filtered
+	 * according to the given time interval and status. Only the calendars
+	 * corresponding to the type set-up of the DAO are retrieved.
+	 *
+	 * @param since
+	 *            the earliest allowed last-update Timestamp
+	 * @param to
+	 *            the latest allowed last-update Timestamp
+	 * @param status
+	 *            'D' for deleted items, 'N' for new items, 'U' for updated
+	 *            items
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+	protected List getItemsHavingStatus(Timestamp since, Timestamp to, char status) throws PIMDBAccessException {
+
+		log.info("\n\n=> Seeking '" + status + "' items " + "in time interval ]" + since + "; " + to + "[");
+
+		Connection 			con       = null;
+		PreparedStatement   ps        = null;
+		List 			    calendars = new ArrayList();
+		ResultSet			rs        = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			if (status == 'D') {
+
+				ps = con.prepareStatement("SELECT guid FROM fnbl_client_mapping WHERE sync_source = 'calendario' AND guid NOT IN (SELECT C.cal_id FROM phpgw_cal as C, phpgw_cal_user as U WHERE C.cal_id = U.cal_id AND U.cal_login = ?) AND principal = ? ORDER BY guid");
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setLong(2, this.principal.getId());
+				rs = ps.executeQuery();
+
+			} else {
+
+				ps = con.prepareStatement(SQL_GET_EXP_PIM_CALENDAR_ID_LIST_BY_USER_TIME_STATUS + SQL_ORDER_BY_ID);
+				ps.setString(1, userId);
+				ps.setLong(2, since.getTime());
+				ps.setLong(3, to.getTime());
+				ps.setString(4, String.valueOf(status));
+				rs = ps.executeQuery();
+
+			}
+
+			while (rs.next()) {
+				calendars.add(Long.toString(rs.getLong(1))); // It's the first and only column
+				log.info("\n=> Item found " + rs.getLong(1));
+			}
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new PIMDBAccessException("\n=> Error listing contacts. " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return calendars;
+	}
+
+	/**
+	 * Retrieves the state of the given item, provided it's been modified after
+	 * a certain moment.
+	 *
+	 * @param uid
+	 *            the UID of the item to be checked (as a String object)
+	 * @param since
+	 *            the Timestamp that the item's lastUpdate field is checked
+	 *            against: if the item has been modified before that moment, an
+	 *            "unchanged" state marker is returned
+	 * @throws PIMDBAccessException
+	 * @return a char identifying either one of the 3 standard states ("new",
+	 *         "deleted", "updated") or the special "unchanged" status, all of
+	 *         them as defined in br.com.br.psync.pim.util.Def
+	 */
+	public char getItemState(String uid, Timestamp since) throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start getItemState");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		char status;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement(SQL_GET_STATUS_BY_ID_USER_TIME);
+
+			ps.setLong(1, Long.parseLong(uid));
+			ps.setString(2, userId);
+			ps.setLong(3, since.getTime());
+
+			rs = ps.executeQuery();
+
+			if (!rs.next()) {
+
+				status = Def.PIM_STATE_UNCHANGED;
+				log.info("\n=> Item " + uid + "'s status wasn't retrieved "
+						+ "because the item hasn't been modified since "
+						+ since + " or the item doesn't exist");
+
+			} else {
+
+				status = rs.getString(1).charAt(0);
+				log.info("\n=> Item " + uid + " has status \'" + status + "\'");
+
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error retrieving item state. " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return status;
+	}
+
+	/**
+	 * Retrieves the UID list of the calendars considered to be "twins" of a
+	 * given contact.
+	 *
+	 * @param c
+	 *            the Calendar object representing the calendar whose twins need
+	 *            be found. In the present implementation, only the following
+	 *            data matter:
+	 *            <UL>
+	 *            <LI>date start
+	 *            <LI>date end
+	 *            <LI>location
+	 *            </UL>
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects) that may be empty but not null
+	 */
+	public List getTwinItems(Calendar c) throws PIMDBAccessException {
+
+		log.info("\n\n\n=> PIMCalendarDAO getTwinItems begin");
+
+		LinkedList twins = new LinkedList();
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+
+		try {
+
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			Date dtStart = null;
+			Date dtEnd = null;
+
+			dtStart = getDateFromString(stringFrom(c.getCalendarContent().getDtStart()));
+			dtEnd = getDateFromString(stringFrom(c.getCalendarContent().getDtEnd()));
+
+			StringBuffer sqlGetCalendarTwinList = new StringBuffer(SQL_GET_EXP_PIM_CALENDAR_ID_LIST_BY_USER);
+
+			String subject = stringFrom(c.getCalendarContent().getSummary(), true); // Empty implies null;
+
+			if ("null".equals(subject)) {
+				subject = null;
+			}
+
+			if (subject == null) {
+				sqlGetCalendarTwinList.append(SQL_AND_NO_SUBJECT_IS_SET);
+			} else {
+				sqlGetCalendarTwinList.append(SQL_AND_SUBJECT_EQUALS_QUESTIONMARK);
+			}
+
+			if (dtStart == null) {
+				sqlGetCalendarTwinList.append(SQL_AND_NO_dtstart_IS_SET);
+			} else {
+				sqlGetCalendarTwinList.append(SQL_AND_dtstart_EQUALS_QUESTIONMARK);
+			}
+
+			if (dtEnd == null) {
+				sqlGetCalendarTwinList.append(SQL_AND_NO_DEND_IS_SET);
+			} else {
+				sqlGetCalendarTwinList.append(SQL_AND_DEND_EQUALS_QUESTIONMARK);
+			}
+
+			sqlGetCalendarTwinList.append(SQL_ORDER_BY_ID);
+
+			StringBuilder sb = new StringBuilder(100);
+			sb.append("\nLooking for items having: ");
+
+			if (subject == null || subject.length() == 0) {
+				sb.append("\n> subject: <N/A>");
+			} else {
+				sb.append("\n> subject: '").append(subject).append('\'');
+			}
+			if (dtStart == null) {
+				sb.append("\n> start date: <N/A>");
+			} else {
+				sb.append("\n> start date: ").append(dtStart);
+			}
+			if (dtEnd == null) {
+				sb.append("\n> end date: <N/A>");
+			} else {
+				sb.append("\n> end date: ").append(dtEnd);
+			}
+
+			log.info(sb.toString());
+
+			ps = con.prepareStatement(sqlGetCalendarTwinList.toString());
+
+			int k = 1;
+
+			ps.setString(k++, userId);
+
+			if (subject != null) {
+				ps.setString(k++, subject);
+			}
+			if (dtStart != null) {
+				ps.setLong(k++, new Long(Long.toString(dtStart.getTime()).substring(0, 10)));
+			}
+			if (dtEnd != null) {
+				ps.setLong(k++, new Long(Long.toString(dtEnd.getTime()).substring(0, 10)));
+			}
+
+			rs = ps.executeQuery();
+
+			long twinId;
+
+			while (rs.next()) {
+
+				twinId = rs.getLong(1); // dend is not relevant in this case
+				log.info("\n\n=> Twin event found: " + twinId);
+
+				twins.add(Long.toString(twinId));
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error retrieving twin. " + e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n\n=> PIMCalendarDAO getTwinItems end");
+
+		return twins;
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	/**
+	 * Creates a ContactWrapper object of Event type from a ResultSet.
+	 *
+	 * @param wrapperId
+	 *            the UID of the wrapper object to be returned
+	 * @param rs
+	 *            the result of the execution of a proper SQL SELECT statement
+	 *            on the phpgw_cal table, with the cursor before its first row
+	 * @return a newly created ContactWrapper initialized with the fields in the
+	 *         result set
+	 * @throws java.sql.SQLException
+	 * @throws NotFoundException
+	 */
+	protected static CalendarWrapper createCalendar(String wrapperId, String user_session ,ResultSet rs, String category) throws NotFoundException, Exception {
+
+		if (!rs.next()) {
+			throw new NotFoundException("\n=> No calendar found.");
+		}
+
+		ResultSetMetaData rsmd = rs.getMetaData();
+		int columnCount = rsmd.getColumnCount();
+
+		String column = null;
+
+		String uid = null;
+		String user = null;
+
+		Date dstart = null;
+		Date dend = null;
+
+		uid = String.valueOf(rs.getLong(SQL_FIELD_ID));
+		user = rs.getString(SQL_FIELD_USERID);
+
+		Calendar cal = new Calendar();
+		Reminder r = new Reminder();
+		CalendarContent c = new Event();
+
+		c.setReminder(r);
+		cal.setEvent((Event) c);
+
+		CalendarWrapper cw = new CalendarWrapper(wrapperId, user, cal);
+
+		for (int i = 1; i <= columnCount; i++) {
+
+			column = rsmd.getColumnName(i);
+
+			if (SQL_FIELD_ID.equalsIgnoreCase(column)) {
+				// Does nothing: field already set at construction time
+			} else if (SQL_FIELD_LAST_UPDATE.equalsIgnoreCase(column)) {
+				cw.setLastUpdate(new Timestamp(rs.getLong(i)));
+			} else if (SQL_FIELD_USERID.equalsIgnoreCase(column)) {
+				// Does nothing: field already set at construction time
+			} else if (SQL_FIELD_CATEGORY.equalsIgnoreCase(column)) {
+				c.getCategories().setPropertyValue(category);
+			} else if (SQL_FIELD_STATUS.equalsIgnoreCase(column)) {
+				cw.setStatus(rs.getString(i).charAt(0));
+			} else if (SQL_FIELD_LOCATION.equalsIgnoreCase(column)) {
+				c.getLocation().setPropertyValue(rs.getString(i));
+			} else if (SQL_FIELD_SUBJECT.equalsIgnoreCase(column)) {
+				c.getSummary().setPropertyValue(rs.getString(i));
+			} else if (SQL_FIELD_BODY.equalsIgnoreCase(column)) {
+				c.getDescription().setPropertyValue(rs.getString(i));
+			} else if (SQL_FIELD_DATE_START.equalsIgnoreCase(column)) {
+				if (rs.getLong(i) != 0) {
+					dstart = new Date(new Timestamp(rs.getLong(i) * 1000).getTime());
+				}
+			} else if (SQL_FIELD_DATE_END.equalsIgnoreCase(column)) {
+				if (rs.getLong(i) != 0) {
+					dend = new Date(new Timestamp(rs.getLong(i) * 1000).getTime());
+				}
+			} else if (SQL_FIELD_SENSITIVITY.equalsIgnoreCase(column)) {
+				Short sensitivity = rs.getShort(i);
+				if (sensitivity == 0) {
+					c.getAccessClass().setPropertyValue(new Short((short) 2));
+				} else {
+					c.getAccessClass().setPropertyValue(new Short((short) 0));
+				}
+			}
+			// Unhandled columns are just ignored
+		}
+
+		c.setAllDay(new Boolean(false));
+
+		if (dstart != null) {
+			c.getDtStart().setPropertyValue(getStringFromDateUTC(dstart));
+		}
+
+		if (dend != null) {
+			c.getDtEnd().setPropertyValue(getStringFromDateUTC(dend));
+		}
+
+		if(!user_session.equals(user)){
+			c.getCategories().setPropertyValue("Compartilhado");
+		}
+
+		return cw;
+	}
+
+	/**
+	 * Converts date in the DB to a date format palm.
+	 *
+	 * @param date
+	 *            it should be in the "yyyyMMdd'T'HHmmss'Z'" format for
+	 *            yyyy-MM-dd HH:MM:SS
+	 * @return a Date object
+	 */
+
+	private static String getStringFromDateUTC(Date date) throws Exception {
+
+		SimpleDateFormat utcDateFormatter = new SimpleDateFormat();
+
+		utcDateFormatter.applyPattern(TimeUtils.PATTERN_UTC);
+		utcDateFormatter.setTimeZone(TimeUtils.TIMEZONE_UTC);
+
+		return utcDateFormatter.format(date);
+	}
+
+	/**
+	 * Converts a String object representing a date into a corresponding Date
+	 * object apt to represent a date in the DB.
+	 *
+	 * @param date
+	 *            it should be in the "yyyyMMdd'T'HHmmss'Z'"
+	 *            format, but will be forced into the right format also if it's
+	 *            in the "yyyyMMdd'T'HHmmss" format (a 'Z' will be appended) or
+	 *            in the "yyyy-MM-dd" format (in this case, the time will be considered 00:00:00)
+	 * @return a Date object
+	 */
+	private static Date getDateFromString(String date) throws ParseException {
+
+		if (date == null || date.length() == 0) {
+			return null;
+		}
+
+		String dateOK = null;
+
+		SimpleDateFormat dateFormatter = new SimpleDateFormat();
+		dateFormatter.applyPattern(TimeUtils.PATTERN_UTC);
+
+		String format = TimeUtils.getDateFormat(date);
+
+		if (format.equals(TimeUtils.PATTERN_YYYY_MM_DD)) {
+
+			dateOK = TimeUtils.convertDateFromInDayFormat(date, "000000", true);
+
+		} else if (format.equals(TimeUtils.PATTERN_UTC_WOZ)) {
+
+			dateOK = date + 'Z'; // the non-all-day formatter wants a 'Z'
+			dateFormatter.setTimeZone(TimeUtils.TIMEZONE_UTC);
+
+		} else {
+
+			dateOK = date; 	// then format should be = TimeUtils.PATTERN_UTC
+			dateFormatter.setTimeZone(TimeUtils.TIMEZONE_UTC);
+		}
+
+		return dateFormatter.parse(dateOK);
+	}
+
+	/**
+	 * Attaches the repetion to the recurrence rule of a calendar on the basis
+	 * of a ResultSet.
+	 *
+	 * @param cw
+	 *            the calendar (as a CalendarWrapper) still lacking information
+	 *            on the exceptions
+	 * @param rs
+	 *            the result of the execution of a proper SQL SELECT statement
+	 *            on the phpgw_cal_repeats table, with the cursor before its
+	 *            first row
+	 * @return the CalendarWrapper object with address information attached
+	 * @throws Exception
+	 */
+	private CalendarWrapper addPIMCalendarExceptions(CalendarWrapper cw, ResultSet rs) throws Exception {
+
+		RecurrencePattern rp = null;
+		short recurrenceType = -1;
+		int interval = 0;
+		short monthOfYear = 0;
+		short dayOfMonth = 0;
+		short dayOfWeekMask = 0;
+		short instance = 0;
+		String startDatePattern = null;
+		String endDatePattern = null;
+		long edate = 0;
+		boolean noend = true;
+		String exceptions_date = null;
+		List exceptions = new Vector();
+
+		if (rs.next()) {
+
+			recurrenceType = Short.parseShort(Long.toString(rs.getLong("recur_type")));
+			interval = Integer.parseInt(Long.toString(rs.getLong("recur_interval")));
+			dayOfWeekMask = Short.parseShort(Long.toString(rs.getLong("recur_data")));
+			startDatePattern = getStringFromDateUTC(new Date(new Timestamp(rs .getLong("datetime") * 1000).getTime()));
+			edate = rs.getLong("recur_enddate");
+			exceptions_date = rs.getString("recur_exception");
+
+			if (edate != 0) {
+				endDatePattern = getStringFromDateUTC(new Date(new Timestamp(edate * 1000).getTime()));
+				noend = false;
+			}
+
+			StringTokenizer exception_date = new StringTokenizer(exceptions_date, ",");
+
+			while (exception_date.hasMoreTokens()) {
+				String date = exception_date.nextToken();
+				SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+				ExceptionToRecurrenceRule etrr = new ExceptionToRecurrenceRule(false, sdf.format(new Date(new Long(date) * 1000)).concat("Z"));
+				exceptions.add(etrr);
+			}
+
+			switch (recurrenceType) {
+			case 1: {
+				recurrenceType = 0;
+				break;
+			}
+			case 2: {
+				recurrenceType = 1;
+				break;
+			}
+			case 3: {
+				recurrenceType = 2;
+				break;
+			}
+			case 4: {
+				recurrenceType = 3;
+				break;
+			}
+			case 5: {
+				recurrenceType = 5;
+				break;
+			}
+			default:
+				break;
+			}
+
+			// Repeticao do evento
+			rp = new RecurrencePattern(recurrenceType, (interval == 0 ? 1 : interval), monthOfYear, dayOfMonth, dayOfWeekMask, instance, startDatePattern, endDatePattern, noend);
+			rp.setExceptions(exceptions);
+			cw.getCalendar().getCalendarContent().setRecurrencePattern(rp);
+		}
+
+		return cw;
+	}
+
+	/**
+	 * Attaches the alarm to the recurrence rule of a calendar on the basis of a
+	 * ResultSet.
+	 *
+	 * @param cw
+	 *            the calendar (as a CalendarWrapper) still lacking information
+	 *            on the exceptions
+	 * @param rs
+	 *            the result of the execution of a proper SQL SELECT statement
+	 *            on the phpgw_async table, with the cursor before its first row
+	 * @return the CalendarWrapper object with address information attached
+	 * @throws Exception
+	 */
+	private CalendarWrapper addPIMCalendarAlarm(CalendarWrapper cw, ResultSet rs) throws Exception {
+
+		if (rs.next()) {
+
+			Reminder rt = null;
+
+			Connection con = null;
+			PreparedStatement ps = null;
+			ResultSet rs1 = null;
+
+			Date datea = null;
+			Date dateb = null;
+			long difDatas = 0;
+
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement("SELECT datetime from phpgw_cal WHERE cal_id = ?");
+			ps.setLong(1, Long.parseLong(cw.getId()));
+			rs1 = ps.executeQuery();
+
+			if (rs1.next()) {
+
+				datea = new Date(new Long(rs.getLong("next") * 1000));
+				dateb = new Date(new Long(rs1.getLong("datetime") * 1000));
+				difDatas = dateb.getTime() - datea.getTime();
+
+				rt = new Reminder();
+				rt.setActive(true);
+				rt.setMinutes(new Integer(Integer.parseInt(new Long(Math.abs(difDatas / (60 * 1000))).toString())));
+				cw.getCalendar().getCalendarContent().setReminder(rt);
+
+			}
+
+			DBTools.close(con, ps, rs1);
+		}
+
+		return cw;
+	}
+
+	private String addCategory(String category) throws Exception {
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		String id_category = null;
+
+		try {
+
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement("SELECT cat_id from phpgw_categories WHERE cat_owner = -1 AND cat_appname = 'calendar' AND cat_name = ?");
+			ps.setString(1, category);
+			rs = ps.executeQuery();
+
+			if(rs.next()){
+				id_category = rs.getString(1);
+			} else {
+
+				ps = con.prepareStatement("SELECT cat_id from phpgw_categories WHERE cat_owner = ? AND cat_appname = 'calendar' AND cat_name = ?");
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setString(2, category);
+				rs = ps.executeQuery();
+
+				if(rs.next()){
+					id_category = rs.getString(1);
+				}
+			}
+
+
+			if(id_category == null){
+
+				ps = con.prepareStatement("INSERT INTO phpgw_categories (cat_owner, cat_access, cat_appname, cat_name, cat_description, cat_data, last_mod) VALUES (?, 'public', 'calendar', ?, '', 'N;', ?)");
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setString(2, category);
+				ps.setLong(3, new Long(Long.toString(new Timestamp(System.currentTimeMillis()).getTime()).substring(0, 10)));
+				ps.executeUpdate();
+
+				ps = con.prepareStatement("SELECT cat_id from phpgw_categories WHERE cat_owner = ? AND cat_appname = 'calendar' AND cat_name = ?");
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setString(2, category);
+				rs = ps.executeQuery();
+
+				rs.next();
+
+				id_category = rs.getString(1);
+
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error search category <" + category + "> " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return id_category;
+	}
+
+	private void addRecurrencePattern(RecurrencePattern rp, long id_cal, String sd) throws Exception {
+
+		Connection 		  con 		= null;
+		PreparedStatement ps 		= null;
+
+		String  dayOfWeekMask 	= null;
+		String  endDatePattern 	= null;
+		String  exception_date 	= "";
+		String  exception_time 	= "";
+
+		int   interval 		 = 0;
+		short recurrenceType = -1;
+		short recur_type 	 = 0;
+
+		try {
+
+			con = getDataSource().getConnection();
+
+			interval = rp.getInterval();
+			recurrenceType = rp.getTypeId();
+			dayOfWeekMask = String.valueOf(rp.getDayOfWeekMask());
+			endDatePattern = rp.getEndDatePattern();
+
+			if (rp.isNoEndDate()) {
+				endDatePattern = "0";
+			} else {
+				endDatePattern = (Long.toString(getDateFromString(endDatePattern).getTime()).substring(0, 10));
+			}
+
+			List exceptions = rp.getExceptions();
+
+			if (!exceptions.isEmpty()) {
+
+				ExceptionToRecurrenceRule etrr = null;
+				exception_time = sd.substring(9, 16); // hora inicial do evento
+
+				int i = 0;
+				int size = exceptions.size();
+
+				for (; i < size - 1; i++) {
+
+					etrr = (ExceptionToRecurrenceRule) exceptions.get(i);
+					exception_date = exception_date + (Long.toString(getDateFromString(etrr.getDate().substring(0, 9).concat(exception_time)).getTime()).substring(0, 10)) + ",";
+				}
+
+				etrr = (ExceptionToRecurrenceRule) exceptions.get(i);
+				exception_date = exception_date + (Long.toString(getDateFromString(etrr.getDate().substring(0, 9).concat(exception_time)).getTime()).substring(0, 10));
+			}
+
+			// Seta o tipo de repetiÃ§Ã£o
+			if (recurrenceType != -1) {
+
+				switch (recurrenceType) {
+				case 0: {
+					recur_type = 1;
+					break;
+				}
+				case 1: {
+					recur_type = 2;
+					break;
+				}
+				case 2: {
+					recur_type = 3;
+					break;
+				}
+				case 3: {
+					recur_type = 4;
+					break;
+				}
+				case 5: {
+					recur_type = 5;
+					break;
+				}
+				default:
+					break;
+				}
+			}
+
+			// Apaga a repeticao do evento se existir
+			deleteRecurrencePattern(id_cal);
+
+			// Insere no banco de dados a repeticao do evento
+			ps = con.prepareStatement(SQL_INSERT_INTO_EXP_PIM_CALENDAR_REPEATS);
+			ps.setLong(1, id_cal);
+			ps.setLong(2, recur_type);
+			ps.setLong(3, 0);
+			ps.setLong(4, new Long(endDatePattern));
+			ps.setLong(5, interval);
+			ps.setLong(6, new Long(dayOfWeekMask));
+			ps.setString(7, exception_date);
+			ps.executeUpdate();
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error event repetitive  " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	private void deleteRecurrencePattern(long id_cal) throws Exception {
+
+		Connection 		  con 		= null;
+		PreparedStatement ps 		= null;
+
+		try {
+
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement("DELETE FROM phpgw_cal_repeats WHERE cal_id = ?");
+			ps.setLong(1, id_cal);
+			ps.executeUpdate();
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error delete event repetitive  " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	private void addReminder(Reminder reminder, long id_cal) throws Exception {
+
+		Connection 		  con 		= null;
+		PreparedStatement ps 		= null;
+
+		try {
+
+			con = getDataSource().getConnection();
+
+			int  minutes = 0;
+			long date    = 0;
+
+			minutes = (reminder.getMinutes() * 60);
+			date = new Long(Long.toString(getDateFromString(reminder.getTime()).getTime()).substring(0, 10));
+
+			// Apaga o alarme do evento
+			deleteReminder(id_cal);
+
+			// Insere no banco de dados o alarme do evento
+			ps = con.prepareStatement(SQL_INSERT_INTO_EXP_PIM_ALARM);
+			ps.setString(1, "cal:" + id_cal + ":0"); // id
+			ps.setLong(2, date); // next
+			ps.setString(3, "i:" + date + ";"); // times
+			ps.setString(4, "calendar.bocalendar.send_alarm"); // method
+			ps.setString(5, "a:5:{s:4:\"time\";i:" + date
+							+ ";s:6:\"offset\";i:" + minutes + ";s:5:\"owner\";i:"
+							+ userId + ";s:7:\"enabled\";i:1;s:6:\"cal_id\";s:"
+							+ new Long(id_cal).toString().length() + ":\"" + id_cal
+							+ "\";}"); // data
+			ps.setLong(6, Long.parseLong(userId)); // account_id
+			ps.executeUpdate();
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error event alarm  " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	private void deleteReminder(long id_cal) throws Exception {
+
+		Connection 		  con 		= null;
+		PreparedStatement ps 		= null;
+
+		try {
+
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement("DELETE FROM phpgw_async WHERE id = ? AND account_id = ?");
+			ps.setString(1, "cal:" + id_cal + ":0");
+			ps.setLong(2, Long.parseLong(userId));
+			ps.executeUpdate();
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("Error delete event alarm  " + e.getMessage());
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMEntityDAO.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMEntityDAO.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMEntityDAO.java	(revision 2082)
@@ -0,0 +1,397 @@
+/**
+ * This class implements methods to access PIM data in a data store.
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.items.dao;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Timestamp;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+import br.com.prognus.psync.exception.PIMDBAccessException;
+import br.com.prognus.psync.util.Def;
+
+import com.funambol.common.pim.common.Property;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+import com.funambol.framework.tools.DBTools;
+import com.funambol.framework.tools.DataSourceTools;
+import com.novell.ldap.LDAPAttribute;
+import com.novell.ldap.LDAPAttributeSet;
+import com.novell.ldap.LDAPConnection;
+import com.novell.ldap.LDAPEntry;
+import com.novell.ldap.LDAPException;
+import com.novell.ldap.LDAPSearchResults;
+import com.novell.ldap.util.Base64;
+
+public abstract class PIMEntityDAO {
+
+	// ------------------------------------------------------------ Private data
+
+	protected static final FunambolLogger log = FunambolLoggerFactory
+			.getLogger(Def.LOGGER_NAME);
+
+	protected String jndiDataSourceName;
+
+	protected DataSource dataSource = null;
+
+	// -------------------------------------------------------------- Properties
+
+	protected String userId;
+
+	public String getUserId() {
+		return userId;
+	}
+
+	// ------------------------------------------------------------ Constructors
+
+	/**
+	 * Creates a new instance of PIMEntityDAO, ready to be linked to a given
+	 * DataSource.
+	 * 
+	 * @param jndiDataSourceName
+	 *            the jndiname of the datasource to use
+	 * @param userId
+	 *            corresponding to the "userid" field of the fun_pim_contact
+	 *            table
+	 * @throws IllegalArgumentException
+	 *             if there are errors looking up the dataSource with the given
+	 *             jndiDataSourceName
+	 */
+	public PIMEntityDAO(String jndiDataSourceName, String userId) {
+
+		Connection con = null;
+		PreparedStatement ps = null;
+
+		try {
+			this.dataSource = DataSourceTools
+					.lookupDataSource(jndiDataSourceName);
+		} catch (NamingException ex) {
+			throw new IllegalArgumentException("Error looking up datasource: "
+					+ jndiDataSourceName, ex);
+		}
+
+		try {
+			con = getDataSource().getConnection();
+
+			PreparedStatement ldap_server = con
+					.prepareStatement("select config_value from phpgw_config WHERE config_name = 'ldap_host' AND config_app = 'phpgwapi'");
+			ResultSet rs1 = ldap_server.executeQuery();
+
+			PreparedStatement ldap_dc = con
+					.prepareStatement("select config_value from phpgw_config WHERE config_name = 'ldap_context' AND config_app = 'phpgwapi'");
+			ResultSet rs2 = ldap_dc.executeQuery();
+
+			rs1.next();
+			rs2.next();
+
+			String server = rs1.getString(1);
+			String dc = rs2.getString(1);
+
+			this.jndiDataSourceName = jndiDataSourceName;
+			this.userId = LdapUID(server, dc, userId);
+
+		} catch (Exception e) {
+			throw new IllegalArgumentException("Error UID ldap", e);
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+
+	}
+
+	// -----------------------------------------------------------Public methods
+
+	protected String LdapUID(String server, String dc, String user)
+			throws Exception {
+
+		LDAPConnection ldap = new LDAPConnection();
+		String searchAttrs[] = { "uidNumber" };
+		String id_owner = null;
+
+		try {
+
+			ldap.connect(server, 389);
+			LDAPSearchResults searchResults = ldap.search(dc,
+					LDAPConnection.SCOPE_SUB, "(uid=" + user + ")",
+					searchAttrs, false);
+
+			while (searchResults.hasMore()) {
+
+				LDAPEntry nextEntry = null;
+				try {
+					nextEntry = searchResults.next();
+				} catch (LDAPException e) {
+					if (e.getResultCode() == LDAPException.LDAP_TIMEOUT
+							|| e.getResultCode() == LDAPException.CONNECT_ERROR)
+						break;
+					else
+						continue;
+				}
+				LDAPAttributeSet attributeSet = nextEntry.getAttributeSet();
+				Iterator allAttributes = attributeSet.iterator();
+
+				while (allAttributes.hasNext()) {
+
+					LDAPAttribute attribute = (LDAPAttribute) allAttributes
+							.next();
+					String attributeName = attribute.getName();
+					Enumeration allValues = attribute.getStringValues();
+
+					if (allValues != null) {
+
+						while (allValues.hasMoreElements()) {
+
+							String Value = (String) allValues.nextElement();
+
+							if (Base64.isLDIFSafe(Value)) {
+							} else {
+								Value = Base64.encode(Value.getBytes());
+							}
+
+							if (attributeName.equalsIgnoreCase("uidNumber")) {
+								id_owner = Value;
+								break;
+							}
+
+						}
+					}
+				}
+			}
+
+			ldap.disconnect();
+
+			if (id_owner == null) {
+				throw new LDAPException();
+			}
+
+		} catch (LDAPException e) {
+			log.info("Error while connecting and binding to LDAP: "
+					+ e.toString());
+		}
+
+		return id_owner;
+	}
+
+	/**
+	 * Looks up the data source, making some guess attempts based on
+	 * jndiDataSourceName.
+	 * 
+	 * @throws Exception
+	 */
+	protected DataSource getDataSource() throws Exception {
+		return dataSource;
+	}
+
+	public abstract List getAllItems() throws PIMDBAccessException;
+
+	public abstract void removeItem(String uid) throws PIMDBAccessException;
+
+	public abstract void removeAllItems() throws PIMDBAccessException;
+
+	public abstract char getItemState(String uid, Timestamp since)
+			throws PIMDBAccessException;
+
+	/**
+	 * Retrieves the UID list of the new items belonging to the user filtered
+	 * according to the given time interval.
+	 * 
+	 * @param since
+	 *            the earliest allowed last-update Timestamp
+	 * @param to
+	 *            the latest allowed last-update Timestamp
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+	public List getNewItems(Timestamp since, Timestamp to)
+			throws PIMDBAccessException {
+
+		log.info("\n\n==>DAO start getNewItems");
+		return getItemsHavingStatus(since, to, 'N', "");
+	}
+
+	/**
+	 * Retrieves the UID list of the deleted items belonging to the user
+	 * filtered according to the given time interval.
+	 * 
+	 * @param since
+	 *            the earliest allowed last-update Timestamp
+	 * @param to
+	 *            the latest allowed last-update Timestamp
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+//Correcao - Inserido o parametro sourceURI - emerson-faria.nobre@serpro.gov.br - 17/set/2009
+	public List getDeletedItems(Timestamp since, Timestamp to, String sourceURI)
+			throws PIMDBAccessException {
+
+		log.info("\n\n==>DAO start getDeletedItems");
+		return getItemsHavingStatus(since, to, 'D', sourceURI);
+	}
+
+	/**
+	 * Retrieves the UID list of the updated items belonging to the user
+	 * filtered according to the given time interval.
+	 * 
+	 * @param since
+	 *            the earliest allowed last-update Timestamp
+	 * @param to
+	 *            the latest allowed last-update Timestamp
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+	public List getUpdatedItems(Timestamp since, Timestamp to)
+			throws PIMDBAccessException {
+
+		log.info("\n\n==>DAO start getUpdatedItems");
+		return getItemsHavingStatus(since, to, 'U', "");
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	/**
+	 * Retrieves the UID list of the items belonging to the user filtered
+	 * according to the given time interval and status.
+	 * 
+	 * @param since
+	 *            the earliest allowed last-update Timestamp
+	 * @param to
+	 *            the latest allowed last-update Timestamp
+	 * @param status
+	 *            'D' for deleted items, 'N' for new items, 'U' for updated
+	 *            items
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+// Alteracao - Adicionado parametro sourceURI - emerson-faria.nobre@serpro.gov.br - 17/set/2009
+	protected abstract List getItemsHavingStatus(Timestamp since, Timestamp to,
+			char status, String sourceURI) throws PIMDBAccessException;
+
+         /**
+	 * Checks (safely) whether the property is unset.
+	 * 
+	 * @param property
+	 *            may be null
+	 * @return false only if the property value is a non-null, but possibly
+	 *         empty (""), string
+	 * 
+	 * @see PIMEntityDAO#stringFrom(Property)
+	 */
+	protected static boolean isNullProperty(Property property) {
+
+		return (property == null || property.getPropertyValueAsString() == null);
+	}
+
+	/**
+	 * Checks (safely) whether the property is unset or set to an empty string.
+	 * 
+	 * @param property
+	 *            may be null
+	 * @return false only if the property value is a non-null non-empty string
+	 * 
+	 * @see PIMEntityDAO#stringFrom(Property, boolean)
+	 */
+	protected static boolean isEmptyProperty(Property property) {
+
+		if (property == null) {
+			return true;
+		}
+		String string = property.getPropertyValueAsString();
+		if (string == null || string.length() == 0) {
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Extract a string from a property in a safe way. An empty string ("") is
+	 * considered as an acceptable value for the property: in such a case, an
+	 * empty String object will be returned.
+	 * 
+	 * @param property
+	 *            may be null
+	 * @return if existing, the property value will be returned as a String
+	 *         object
+	 */
+	protected static String stringFrom(Property property) {
+
+		if (property == null) {
+			return null;
+		}
+		return property.getPropertyValueAsString();
+	}
+
+	/**
+	 * Extract a string from a property in a safe way. This method is not
+	 * currently used, but it could be useful in the future for determining the
+	 * behaviour of the connector when dealing with empty properties. A field
+	 * whose value is extracted with stringFrom(..., true) will not be updated
+	 * in case its value is set to ""; a field whose value is extracted with
+	 * stringFrom(..., false) will be considered as explicitly kept blank if its
+	 * value is "". This means that single field deletions can be made tunable.
+	 * 
+	 * @param property
+	 *            may be null
+	 * @param emptyImpliesNull
+	 *            if true, an empty string ("") will be treated as if it were
+	 *            null; otherwise, in such a case an empty String object will be
+	 *            returned
+	 * 
+	 * @return if existing (and not empty if emptyImpliesNull is true), the
+	 *         property value will be returned as a String object
+	 */
+	protected static String stringFrom(Property property,
+			boolean emptyImpliesNull) {
+
+		if (property == null) {
+			return null;
+		}
+
+		String string = property.getPropertyValueAsString();
+		if (string == null || !emptyImpliesNull) {
+			return string;
+		}
+
+		if (string.length() == 0) {
+			return null;
+		}
+		return string;
+	}
+
+	/**
+	 * Gets a substring in a safe way.
+	 * 
+	 * @param string
+	 *            may be null or longer than maxLength
+	 * @param maxLength
+	 * @return null if the string is null, a truncated substring of string
+	 *         otherwise
+	 */
+	protected static String truncate(String string, int maxLength) {
+		if (string == null || string.length() <= maxLength) {
+			return string;
+		}
+		return string.substring(0, maxLength);
+	}
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMContactDAO.java.original
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMContactDAO.java.original	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/dao/PIMContactDAO.java.original	(revision 2082)
@@ -0,0 +1,1346 @@
+/**
+ * This class implements methods to access PIM contact data in a data store.
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.items.dao;
+
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import br.com.prognus.psync.exception.PIMDBAccessException;
+import br.com.prognus.psync.items.model.ContactWrapper;
+import br.com.prognus.psync.util.Def;
+
+import com.funambol.common.pim.contact.BusinessDetail;
+import com.funambol.common.pim.contact.Contact;
+import com.funambol.common.pim.contact.Email;
+import com.funambol.common.pim.contact.Name;
+import com.funambol.common.pim.contact.PersonalDetail;
+import com.funambol.common.pim.contact.Phone;
+import com.funambol.framework.security.Sync4jPrincipal;
+import com.funambol.framework.server.store.NotFoundException;
+import com.funambol.framework.tools.DBTools;
+
+public class PIMContactDAO extends PIMEntityDAO {
+
+	// --------------------------------------------------------------- Constants
+
+	private static final String SQL_ORDER_BY_ID = "ORDER BY id_contact";
+
+	private static final String SQL_GET_CONTACT_ID_LIST = "SELECT id_contact FROM phpgw_cc_contact ";
+
+	private static final String SQL_GET_CONTACT_ID_LIST_BY_USER = SQL_GET_CONTACT_ID_LIST
+			+ "WHERE id_owner = ?";
+
+	private static final String SQL_GET_CONTACT_BY_ID_USER = "SELECT id_contact, id_owner, last_update, last_status, given_names, family_names, birthdate, category FROM phpgw_cc_contact WHERE id_contact = ? AND id_owner = ? LIMIT 1";
+
+	private static final String SQL_GET_CONTACT_ITEM_BY_ID = "SELECT connection_name, connection_value, id_typeof_contact_connection FROM phpgw_cc_connections AS CO LEFT JOIN phpgw_cc_contact_conns AS CC ON CO.id_connection = CC.id_connection WHERE id_contact = ?";
+
+	private static final String SQL_GET_STATUS_BY_ID_USER_TIME = "SELECT last_status FROM phpgw_cc_contact WHERE id_contact = ? AND id_owner = ? AND last_update > ? LIMIT 1 ";
+
+	private static final String SQL_GET_FNBL_PIM_CONTACT_ID_LIST_BY_USER_TIME_STATUS = SQL_GET_CONTACT_ID_LIST
+			+ "WHERE id_owner = ? AND last_update > ? AND last_update < ? AND last_status = ? ";
+
+	private static final String SQL_GET_CONTACT_TWIN_ID_LIST = "SELECT id_contact "
+			+ "FROM phpgw_cc_contact "
+			+ "WHERE (id_owner = ?) "
+			+ "AND ((given_names is null AND (?) = '') "
+			+ "OR (lower(given_names) = ?)) "
+			+ "AND ((family_names is null AND (?) = '') "
+			+ "OR (lower(family_names) = ?)) ";
+
+	private static final String SQL_INSERT_INTO_FNBL_PIM_CONTACT = "INSERT INTO phpgw_cc_contact "
+			+ "(id_contact, id_owner, id_status, given_names, family_names, names_ordered, birthdate, category, last_update, last_status ) "
+			+ "VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ";
+
+	private static final String SQL_INSERT_INTO_CONTACT_ITEM = "INSERT INTO phpgw_cc_connections "
+			+ "(id_connection, connection_name, connection_value, connection_is_default) "
+			+ "VALUES (?, ?, ?, ?) ";
+
+	private static final String SQL_ID_MAX_CONNECTIONS = "select max(id_connection) from phpgw_cc_connections";
+
+	private static final String SQL_INSERT_INTO_FNBL_CONNECTIONS = "INSERT INTO phpgw_cc_contact_conns (id_contact, id_connection, id_typeof_contact_connection) VALUES (?, ?, ?)";
+
+	private static final String SQL_UPDATE_FNBL_PIM_CONTACT_BEGIN = "UPDATE phpgw_cc_contact SET ";
+
+	private static final String SQL_UPDATE_FNBL_PIM_CONTACT_END = " WHERE id_contact = ? AND id_owner = ? ";
+
+	private static final String SQL_EQUALS_QUESTIONMARK = " = ?";
+
+	private static final String SQL_EQUALS_QUESTIONMARK_COMMA = " = ?, ";
+
+	protected static final String SQL_FIELD_ID = "id_contact";
+
+	protected static final String SQL_FIELD_USERID = "id_owner";
+
+	protected static final String SQL_FIELD_LAST_UPDATE = "last_update";
+
+	protected static final String SQL_FIELD_STATUS = "last_status";
+
+	protected static final String SQL_FIELD_FIRST_NAME = "given_names";
+
+	protected static final String SQL_FIELD_LAST_NAME = "family_names";
+
+	protected static final String SQL_FIELD_NAME = "names_ordered";
+
+	protected static final String SQL_FIELD_NICK = "alias";
+
+	protected static final String SQL_FIELD_BIRTHDAY = "birthdate";
+
+	protected static final String SQL_FIELD_CATEGORY = "category";
+
+	protected static final int SQL_FIRSTNAME_DIM = 49;
+
+	protected static final int SQL_LASTNAME_DIM = 50;
+
+	protected static final int SQL_EMAIL_DIM = 50;
+
+	protected static final int SQL_PHONE_DIM = 50;
+
+	protected static final int SQL_CATEGORY_DIM = 20;
+
+	protected static final String TYPE_EMAIL_1_ADDRESS = "Principal";
+
+	protected static final String FIELD_EMAIL_1_ADDRESS = "Email1Address";
+
+	protected static final String TYPE_EMAIL_2_ADDRESS = "Alternativo";
+
+	protected static final String FIELD_EMAIL_2_ADDRESS = "OtherEmail2Address";
+
+	protected static final String TYPE_PRIMARY_TELEPHONE_NUMBER = "Principal";
+
+	protected static final String FIELD_PRIMARY_TELEPHONE_NUMBER = "PrimaryTelephoneNumber";
+
+	protected static final String TYPE_HOME_TELEPHONE_NUMBER = "Residencial";
+
+	protected static final String FIELD_HOME_TELEPHONE_NUMBER = "HomeTelephoneNumber";
+
+	protected static final String TYPE_MOBILE_TELEPHONE_NUMBER = "Celular";
+
+	protected static final String FIELD_MOBILE_TELEPHONE_NUMBER = "MobileTelephoneNumber";
+
+	protected static final String TYPE_BUSINESS_TELEPHONE_NUMBER = "Comercial";
+
+	protected static final String FIELD_BUSINESS_TELEPHONE_NUMBER = "BusinessTelephoneNumber";
+
+	protected static final String TYPE_BUSINESS_FAX_NUMBER = "Fax";
+
+	protected static final String FIELD_BUSINESS_FAX_NUMBER = "BusinessFaxNumber";
+
+	protected static final String TYPE_PAGER_NUMBER = "Pager";
+
+	protected static final String FIELD_PAGER_NUMBER = "PagerNumber";
+
+	protected static final String SQL_FIELD_ITEM_NAME = "connection_name";
+
+	protected static final String SQL_FIELD_ITEM_VALUE = "connection_value";
+
+	protected static final String SQL_FIELD_ITEM_TYPE = "id_typeof_contact_connection";
+
+	private Sync4jPrincipal principal = null;
+
+	// -------------------------------------------------------------
+	// Constructors
+
+	/**
+	 * @see PIMEntityDAO#PIMEntityDAO(String, String)
+	 */
+	public PIMContactDAO(String jndiDataSourceName, Sync4jPrincipal principal) {
+
+		super(jndiDataSourceName, principal.getUsername());
+
+		log.info("\n\n=> Created new PIMContactDAO for data source "
+				+ jndiDataSourceName + " and user ID LDAP " + userId
+				+ " user login " + principal.getUsername() + "\n");
+
+		this.principal = principal;
+	}
+
+	// ----------------------------------------------------------- Public
+	// methods
+
+	/**
+	 * Adds a contact. If necessary, a new ID is generated and set in the
+	 * ContactWrapper.
+	 *
+	 * @param c
+	 *            as a ContactWrapper object, usually without an ID set.
+	 * @throws PIMDBAccessException
+	 * @throws SQLException
+	 *
+	 * @see ContactWrapper
+	 */
+	public void addItem(ContactWrapper cw) throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMContactDAO addItem begin\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+
+		long id_contact = 0;
+		long id_connection = 0;
+
+		PersonalDetail personalDetail = null;
+		BusinessDetail businessDetail = null;
+
+		Name name = null;
+		Phone phone = null;
+		Email email = null;
+
+		List<Email> emails = new ArrayList();
+		List<Phone> phones = new ArrayList();
+
+		String phoneType = null;
+		String firstName = null;
+		String lastName = null;
+		String birthday = null;
+		String type = null;
+		String category = null;
+
+		StringBuffer fullName = null;
+		Date anniversary = null;
+
+		try {
+
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			Contact c = cw.getContact();
+			Timestamp lastUpdate = cw.getLastUpdate();
+
+			personalDetail = c.getPersonalDetail();
+			businessDetail = c.getBusinessDetail();
+			name = c.getName();
+
+			lastUpdate = (lastUpdate == null) ? new Timestamp(System
+					.currentTimeMillis()) : lastUpdate;
+
+			if (personalDetail != null) {
+				emails.addAll(personalDetail.getEmails());
+				phones.addAll(personalDetail.getPhones());
+				birthday = personalDetail.getBirthday();
+				category = stringFrom(c.getCategories());
+			}
+
+			if (businessDetail != null) {
+				emails.addAll(businessDetail.getEmails());
+				phones.addAll(businessDetail.getPhones());
+			}
+
+			if (name != null) {
+				firstName = stringFrom(name.getFirstName());
+				lastName = stringFrom(name.getLastName());
+			}
+
+			// consulta o id maximo da tabela contatos
+			ps = con
+					.prepareStatement("select max(id_contact) from phpgw_cc_contact  limit 1");
+			rs = ps.executeQuery();
+
+			if (rs.next()) {
+				long id = rs.getInt(1);
+				id_contact = (id == 0) ? 1 : ++id;
+			}
+
+			cw.setId(Long.toString(id_contact));
+
+			// Formata o nome completo do contato
+			fullName = new StringBuffer();
+
+			if (firstName != null && firstName.length() > 0)
+				fullName.append(truncate(firstName, SQL_FIRSTNAME_DIM));
+			if (lastName != null && lastName.length() > 0) {
+				if (firstName != null && firstName.length() > 0
+						&& lastName != null && lastName.length() > 0)
+					fullName.append(" " + truncate(lastName, SQL_LASTNAME_DIM));
+				else
+					fullName.append(truncate(lastName, SQL_LASTNAME_DIM));
+			}
+
+			// Formata a data de aniversario do contato
+			if ((birthday != null) && (!birthday.equals(""))) {
+				anniversary = new java.sql.Date((new SimpleDateFormat(
+						"yyyy-MM-dd").parse(birthday)).getTime());
+			}
+
+			// Prepara os dados para inserir o contato no banco de dados
+			ps = con.prepareStatement(SQL_INSERT_INTO_FNBL_PIM_CONTACT);
+			ps.setLong(1, id_contact); // id_contact
+			ps.setLong(2, Long.parseLong(userId)); // id_owner
+			ps.setLong(3, 1); // id_status
+			ps.setString(4, truncate(firstName, SQL_FIRSTNAME_DIM)); // given_names
+			ps.setString(5, truncate(lastName, SQL_LASTNAME_DIM)); // family_names
+			ps.setString(6, fullName.toString()); // names_ordered
+			ps.setDate(7, anniversary); // birthdate
+			ps.setString(8, truncate(category, SQL_CATEGORY_DIM)); // category
+			ps.setLong(9, lastUpdate.getTime()); // last_update
+			ps.setString(10, String.valueOf(Def.PIM_STATE_NEW)); // last_status
+			ps.executeUpdate();
+
+			// emails
+			if (!emails.isEmpty()) {
+
+				for (int i = 0, l = emails.size(); i < l; i++) {
+
+					email = emails.get(i);
+					Boolean type_email;
+
+					if ((FIELD_EMAIL_1_ADDRESS).equals(email.getEmailType())) {
+						type = TYPE_EMAIL_1_ADDRESS;
+						type_email = true;
+					} else if ((FIELD_EMAIL_2_ADDRESS).equals(email
+							.getEmailType())) {
+						type = TYPE_EMAIL_2_ADDRESS;
+						type_email = false;
+					} else {
+						continue; // Unknown property: saves nothing
+					}
+
+					String emailValue = email.getPropertyValueAsString();
+
+					if (emailValue != null && emailValue.length() != 0) {
+
+						if (emailValue.length() > SQL_EMAIL_DIM) {
+							emailValue = emailValue.substring(0, SQL_EMAIL_DIM);
+						}
+
+						// Verifica o id maximo da tabela phpgw_cc_connections
+						ps = con.prepareStatement(SQL_ID_MAX_CONNECTIONS);
+						rs = ps.executeQuery();
+
+						if (rs.next()) {
+							long id = rs.getInt(1);
+							id_connection = (id == 0) ? 1 : ++id;
+						}
+
+						// Insere os emails do contato na tabela
+						// phpgw_cc_connections
+						ps = con.prepareStatement(SQL_INSERT_INTO_CONTACT_ITEM);
+						ps.setLong(1, id_connection);
+						ps.setString(2, type);
+						ps.setString(3, emailValue);
+						ps.setBoolean(4, type_email);
+						ps.executeUpdate();
+
+						// Insere as referencias de emails do contato na tabela
+						// phpgw_cc_connections
+						ps = con
+								.prepareStatement(SQL_INSERT_INTO_FNBL_CONNECTIONS);
+						ps.setLong(1, id_contact);
+						ps.setLong(2, id_connection);
+						ps.setLong(3, 1);
+						ps.executeUpdate();
+					}
+
+				}
+			}
+
+			// phones
+			if (!phones.isEmpty()) {
+
+				Boolean type_phone = true;
+
+				for (int i = 0, l = phones.size(); i < l; i++) {
+
+					phone = phones.get(i);
+					phoneType = phone.getPhoneType();
+
+					if ((FIELD_PRIMARY_TELEPHONE_NUMBER).equals(phoneType)) {
+						type = TYPE_PRIMARY_TELEPHONE_NUMBER;
+					} else if ((FIELD_HOME_TELEPHONE_NUMBER).equals(phoneType)) {
+						type = TYPE_HOME_TELEPHONE_NUMBER;
+					} else if ((FIELD_MOBILE_TELEPHONE_NUMBER)
+							.equals(phoneType)) {
+						type = TYPE_MOBILE_TELEPHONE_NUMBER;
+					} else if ((FIELD_BUSINESS_TELEPHONE_NUMBER)
+							.equals(phoneType)) {
+						type = TYPE_BUSINESS_TELEPHONE_NUMBER;
+					} else if ((FIELD_BUSINESS_FAX_NUMBER).equals(phoneType)) {
+						type = TYPE_BUSINESS_FAX_NUMBER;
+					} else if ((FIELD_PAGER_NUMBER).equals(phoneType)) {
+						type = TYPE_PAGER_NUMBER;
+					} else {
+						continue; // Unknown property: saves nothing
+					}
+
+					String phoneValue = phone.getPropertyValueAsString();
+
+					if (phoneValue != null && phoneValue.length() != 0) {
+
+						if (phoneValue.length() > SQL_PHONE_DIM) {
+							phoneValue = phoneValue.substring(0, SQL_PHONE_DIM);
+						}
+
+						ps = con.prepareStatement(SQL_ID_MAX_CONNECTIONS);
+						rs = ps.executeQuery();
+
+						if (rs.next()) {
+							long id = rs.getInt(1);
+							id_connection = (id == 0) ? 1 : ++id;
+						}
+
+						// Insere os telefones do contato na tabela
+						// phpgw_cc_connections
+						ps = con.prepareStatement(SQL_INSERT_INTO_CONTACT_ITEM);
+						ps.setLong(1, id_connection);
+						ps.setString(2, type);
+						ps.setString(3, phoneValue);
+						ps.setBoolean(4, type_phone);
+						ps.executeUpdate();
+
+						type_phone = false; // os demais telefones serao setados
+						// como nao padrao
+
+						// Insere as referencias de telefones do contato na
+						// tabela phpgw_cc_connections
+						ps = con
+								.prepareStatement(SQL_INSERT_INTO_FNBL_CONNECTIONS);
+						ps.setLong(1, id_contact);
+						ps.setLong(2, id_connection);
+						ps.setLong(3, 2);
+						ps.executeUpdate();
+					}
+
+				}
+			}
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error adding contact.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=> Added item with ID " + id_contact + "\n");
+		log.info("\n\n=> PIMContactDAO addItem end\n");
+	}
+
+	/**
+	 * Updates a contact.
+	 *
+	 * @param c
+	 *            as a ContactWrapper object. If its last update time is null,
+	 *            then it's set to the current time.
+	 * @return the UID of the contact
+	 * @throws PIMDBAccessException
+	 *
+	 * @see ContactWrapper
+	 */
+	public String updateItem(ContactWrapper cw) throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMContactDAO updateItem begin\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+
+		long id_connection = 0;
+		long id_contact = 0;
+
+		PersonalDetail personalDetail = null;
+		BusinessDetail businessDetail = null;
+
+		Name name = null;
+		Phone phone = null;
+		Email email = null;
+		String type = null;
+
+		List<Email> emails = new ArrayList();
+		List<Phone> phones = new ArrayList();
+
+		String phoneType = null;
+		StringBuffer queryUpdateFunPimContact = null;
+
+		String firstName = null;
+		String lastName = null;
+		StringBuffer fullName = null;
+		String birthday = null;
+		String category = null;
+		Date anniversary = null;
+
+		try {
+
+			Timestamp lastUpdate = (cw.getLastUpdate() == null) ? new Timestamp(
+					System.currentTimeMillis())
+					: cw.getLastUpdate();
+
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			Contact c = cw.getContact();
+
+			personalDetail = c.getPersonalDetail();
+			businessDetail = c.getBusinessDetail();
+			name = c.getName();
+			id_contact = Long.parseLong(cw.getId());
+
+			if (personalDetail != null) {
+				emails.addAll(personalDetail.getEmails());
+				phones.addAll(personalDetail.getPhones());
+				birthday = personalDetail.getBirthday();
+				category = stringFrom(c.getCategories());
+			}
+
+			if (businessDetail != null) {
+				emails.addAll(businessDetail.getEmails());
+				phones.addAll(businessDetail.getPhones());
+			}
+
+			if (name != null) {
+				firstName = stringFrom(name.getFirstName());
+				lastName = stringFrom(name.getLastName());
+			}
+
+			if (firstName != null && firstName.length() > SQL_FIRSTNAME_DIM) {
+				firstName = firstName.substring(0, SQL_FIRSTNAME_DIM);
+			}
+			if (lastName != null && lastName.length() > SQL_LASTNAME_DIM) {
+				lastName = lastName.substring(0, SQL_LASTNAME_DIM);
+			}
+			if (category != null && category.length() > SQL_CATEGORY_DIM) {
+				category = category.substring(0, SQL_CATEGORY_DIM);
+			}
+
+			// Formata o nome completo do contato
+			fullName = new StringBuffer();
+
+			if (firstName != null && firstName.length() > 0)
+				fullName.append(truncate(firstName, SQL_FIRSTNAME_DIM));
+			if (lastName != null && lastName.length() > 0) {
+				if (firstName != null && firstName.length() > 0
+						&& lastName != null && lastName.length() > 0)
+					fullName.append(" " + truncate(lastName, SQL_LASTNAME_DIM));
+				else
+					fullName.append(truncate(lastName, SQL_LASTNAME_DIM));
+			}
+
+			// Formata a data de aniversario do contato
+			if ((birthday != null) && (!birthday.equals(""))) {
+				anniversary = new java.sql.Date((new SimpleDateFormat(
+						"yyyy-MM-dd").parse(birthday)).getTime());
+			} else {
+				anniversary = null;
+			}
+
+			// Prepara os dados do contato para serem atualizados no banco de
+			// dados
+			queryUpdateFunPimContact = new StringBuffer();
+			queryUpdateFunPimContact.append(SQL_UPDATE_FNBL_PIM_CONTACT_BEGIN
+					+ SQL_FIELD_LAST_UPDATE + SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact.append(SQL_FIELD_FIRST_NAME
+					+ SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact.append(SQL_FIELD_LAST_NAME
+					+ SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact.append(SQL_FIELD_NAME
+					+ SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact.append(SQL_FIELD_BIRTHDAY
+					+ SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact.append(SQL_FIELD_CATEGORY
+					+ SQL_EQUALS_QUESTIONMARK_COMMA);
+			queryUpdateFunPimContact
+					.append(SQL_FIELD_STATUS + SQL_EQUALS_QUESTIONMARK
+							+ SQL_UPDATE_FNBL_PIM_CONTACT_END);
+
+			ps = con.prepareStatement(queryUpdateFunPimContact.toString());
+
+			ps.setLong(1, lastUpdate.getTime()); // last_update
+			ps.setString(2, firstName); // given_names
+			ps.setString(3, lastName); // family_names
+			ps.setString(4, fullName.toString()); // names_ordered
+			ps.setDate(5, anniversary); // birthdate
+			ps.setString(6, category); // category
+			ps.setString(7, String.valueOf(Def.PIM_STATE_UPDATED)); // last_status
+			ps.setLong(8, id_contact); // id_contact
+			ps.setLong(9, Long.parseLong(userId)); // id_owner
+			ps.executeUpdate();
+
+			// emails
+
+			// Apaga os emails do contato na tabela phpgw_cc_connections
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_connections WHERE id_connection IN (SELECT id_connection FROM phpgw_cc_contact_conns WHERE id_contact = ? AND id_typeof_contact_connection = 1)");
+			ps.setLong(1, id_contact);
+			ps.executeUpdate();
+
+			// Apaga as referencias de emails do contato na tabela
+			// phpgw_cc_contact_conns
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_conns WHERE id_contact = ? AND id_typeof_contact_connection = 1");
+			ps.setLong(1, id_contact);
+			ps.executeUpdate();
+
+			if (!emails.isEmpty()) {
+
+				boolean type_mail = true;
+
+				for (int i = 0, l = emails.size(); i < l; i++) {
+
+					email = emails.get(i);
+
+					if ((FIELD_EMAIL_1_ADDRESS).equals(email.getEmailType())) {
+						type = TYPE_EMAIL_1_ADDRESS;
+						type_mail = true;
+					} else if ((FIELD_EMAIL_2_ADDRESS).equals(email
+							.getEmailType())) {
+						type = TYPE_EMAIL_2_ADDRESS;
+						type_mail = false;
+					} else {
+						continue; // no save unknown property
+					}
+
+					String emailValue = email.getPropertyValueAsString();
+					emailValue = truncate(emailValue, SQL_EMAIL_DIM);
+
+					if (emailValue != null && emailValue.length() != 0) {
+
+						// Verifica o id maximo da tabela phpgw_cc_connections
+						ps = con.prepareStatement(SQL_ID_MAX_CONNECTIONS);
+						rs = ps.executeQuery();
+
+						if (rs.next()) {
+							long id = rs.getInt(1);
+							id_connection = (id == 0) ? 1 : ++id;
+						}
+
+						// Insere os emails do contato na tabela
+						// phpgw_cc_connections
+						ps = con.prepareStatement(SQL_INSERT_INTO_CONTACT_ITEM);
+						ps.setLong(1, id_connection);
+						ps.setString(2, type);
+						ps.setString(3, emailValue);
+						ps.setBoolean(4, type_mail);
+						ps.executeUpdate();
+
+						// Insere as referencias de emails do contato na tabela
+						// phpgw_cc_connections
+						ps = con
+								.prepareStatement(SQL_INSERT_INTO_FNBL_CONNECTIONS);
+						ps.setLong(1, id_contact);
+						ps.setLong(2, id_connection);
+						ps.setLong(3, 1);
+						ps.executeUpdate();
+					}
+				}
+			}
+
+			// phones
+
+			// Apaga os telefones do contato na tabela phpgw_cc_connections
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_connections WHERE id_connection IN (SELECT id_connection FROM phpgw_cc_contact_conns WHERE id_contact = ? AND id_typeof_contact_connection = 2)");
+			ps.setLong(1, id_contact);
+			ps.executeUpdate();
+
+			// Apaga as referencias de telefones do contato na tabela
+			// phpgw_cc_contact_conns
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_conns WHERE id_contact = ? AND id_typeof_contact_connection = 2");
+			ps.setLong(1, id_contact);
+			ps.executeUpdate();
+
+			if (!phones.isEmpty()) {
+
+				boolean type_fone = true; // Seta o primeiro telefone como
+				// principal no expresso
+
+				for (int i = 0, l = phones.size(); i < l; i++) {
+
+					phone = phones.get(i);
+					phoneType = phone.getPhoneType();
+
+					if ((FIELD_PRIMARY_TELEPHONE_NUMBER).equals(phoneType)) {
+						type = TYPE_PRIMARY_TELEPHONE_NUMBER;
+					} else if ((FIELD_HOME_TELEPHONE_NUMBER).equals(phoneType)) {
+						type = TYPE_HOME_TELEPHONE_NUMBER;
+					} else if ((FIELD_MOBILE_TELEPHONE_NUMBER)
+							.equals(phoneType)) {
+						type = TYPE_MOBILE_TELEPHONE_NUMBER;
+					} else if ((FIELD_BUSINESS_TELEPHONE_NUMBER)
+							.equals(phoneType)) {
+						type = TYPE_BUSINESS_TELEPHONE_NUMBER;
+					} else if ((FIELD_BUSINESS_FAX_NUMBER).equals(phoneType)) {
+						type = TYPE_BUSINESS_FAX_NUMBER;
+					} else if ((FIELD_PAGER_NUMBER).equals(phoneType)) {
+						type = TYPE_PAGER_NUMBER;
+					} else {
+						continue; // Unknown property: saves nothing
+					}
+
+					String phoneValue = phone.getPropertyValueAsString();
+					phoneValue = truncate(phoneValue, SQL_EMAIL_DIM);
+
+					if (phoneValue != null && phoneValue.length() != 0) {
+
+						// Verifica o id maximo da tabela phpgw_cc_connections
+						ps = con.prepareStatement(SQL_ID_MAX_CONNECTIONS);
+						rs = ps.executeQuery();
+
+						if (rs.next()) {
+							long id = rs.getInt(1);
+							id_connection = (id == 0) ? 1 : ++id;
+						}
+
+						// Insere os telefones do contato na tabela
+						// phpgw_cc_connections
+						ps = con.prepareStatement(SQL_INSERT_INTO_CONTACT_ITEM);
+						ps.setLong(1, id_connection);
+						ps.setString(2, type);
+						ps.setString(3, phoneValue);
+						ps.setBoolean(4, type_fone);
+						ps.executeUpdate();
+
+						type_fone = false; // os demais telefones serao setados
+						// como nao padrao
+
+						// Insere as referencias de telefones do contato na
+						// tabela phpgw_cc_connections
+						ps = con
+								.prepareStatement(SQL_INSERT_INTO_FNBL_CONNECTIONS);
+						ps.setLong(1, id_contact);
+						ps.setLong(2, id_connection);
+						ps.setLong(3, 2);
+						ps.executeUpdate();
+					}
+				}
+			}
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error updating contact.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=> Update item with ID " + id_contact + "\n");
+		log.info("\n\n=> PIMContactDAO updateItem end\n");
+
+		return Long.toString(id_contact);
+	}
+
+	/**
+	 * Removes the contact with given UID and sets its last_update field,
+	 * provided it has the same userId as this DAO. The deletion is soft
+	 * (reversible).
+	 *
+	 * @param uid
+	 *            corresponds to the id field in the phpgw_cc_contact table
+	 * @throws PIMDBAccessException
+	 */
+	public void removeItem(String uid) throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start removeItem " + uid + "\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_addresses WHERE id_address IN (SELECT id_address FROM phpgw_cc_contact_addrs WHERE id_contact = ?)");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_addrs WHERE id_contact = ?");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_connections WHERE id_connection IN (SELECT id_connection FROM phpgw_cc_contact_conns WHERE id_contact = ?)");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_conns WHERE id_contact = ?");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact WHERE id_contact = ?");
+			ps.setLong(1, Long.parseLong(uid));
+			ps.executeUpdate();
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+
+			e.printStackTrace();
+			throw new PIMDBAccessException("\nError deleting contact.\n", e);
+
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	/**
+	 * Removes a contact, provided it has the same userId as this DAO. The
+	 * deletion is soft (reversible).
+	 *
+	 * @param contact
+	 *            whence the UID and the last update Date are extracted
+	 * @throws PIMDBAccessException
+	 */
+	public void removeItem(ContactWrapper contact) throws PIMDBAccessException {
+
+		removeItem(contact.getId());
+	}
+
+	/**
+	 * Removes all contacts, provided it has the same userId as this DAO. The
+	 * deletion is soft (reversible).
+	 *
+	 * @throws PIMDBAccessException
+	 */
+	public void removeAllItems() throws PIMDBAccessException {
+
+		log.info("\n\n =>DAO start removeAllItems\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+			con.setAutoCommit(false);
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_addresses WHERE id_address IN (SELECT id_address FROM phpgw_cc_contact_addrs WHERE id_contact IN(SELECT id_contact FROM phpgw_cc_contact WHERE id_owner = ?))");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_addrs WHERE id_contact IN(SELECT id_contact FROM phpgw_cc_contact WHERE id_owner = ?)");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_connections WHERE id_connection IN (SELECT id_connection FROM phpgw_cc_contact_conns WHERE id_contact IN(SELECT id_contact FROM phpgw_cc_contact WHERE id_owner = ?))");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact_conns WHERE id_contact IN(SELECT id_contact FROM phpgw_cc_contact WHERE id_owner = ?)");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			ps = con
+					.prepareStatement("DELETE FROM phpgw_cc_contact WHERE id_owner = ?");
+			ps.setLong(1, Long.parseLong(userId));
+			ps.executeUpdate();
+
+			con.commit();
+			con.setAutoCommit(true);
+
+		} catch (Exception e) {
+
+			throw new PIMDBAccessException("\n=> Error deleting contacts.\n", e);
+
+		} finally {
+			DBTools.close(con, ps, null);
+		}
+	}
+
+	/**
+	 * Retrieves the UID list of all contacts belonging to the user.
+	 *
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+	public List getAllItems() throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMContactDAO getAllItems begin\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		List contacts = new ArrayList();
+		ResultSet rs = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement(SQL_GET_CONTACT_ID_LIST_BY_USER
+					+ SQL_ORDER_BY_ID);
+			ps.setLong(1, Long.parseLong(userId));
+
+			rs = ps.executeQuery();
+
+			while (rs.next()) {
+				contacts.add(Long.toString(rs.getLong(1))); // It's the first
+				// and only column
+			}
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new PIMDBAccessException("\n=> Error listing contacts.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=> PIMContactDAO getAllItems end\n");
+
+		return contacts;
+	}
+
+	/**
+	 * Gets the contact with given UID, provided it has the same userId as this
+	 * DAO.
+	 *
+	 * @param uid
+	 *            corresponds to the id field in the fnbl_pim_contact table
+	 * @throws PIMDBAccessException
+	 * @return the contact as a ContactWrapper object.
+	 */
+	public ContactWrapper getItem(String uid) throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start getItem " + uid + "\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		ContactWrapper c;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement(SQL_GET_CONTACT_BY_ID_USER);
+			ps.setLong(1, Long.parseLong(uid));
+			ps.setLong(2, Long.parseLong(userId));
+			rs = ps.executeQuery();
+			c = createContact(uid, rs);
+
+			ps = con.prepareStatement(SQL_GET_CONTACT_ITEM_BY_ID);
+			ps.setLong(1, Long.parseLong(uid));
+			rs = ps.executeQuery();
+
+			try {
+				addPIMContactItems(c, rs);
+			} catch (SQLException sqle) {
+				throw new SQLException(
+						"\nError while adding extra PIM contact "
+								+ "information.\n" + sqle.getMessage(), sqle
+								.getSQLState());
+			}
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new PIMDBAccessException("\n=> Error seeking contact.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return c;
+	}
+
+	/**
+	 * Retrieves the UID list of the contacts considered to be "twins" of a
+	 * given contact.
+	 *
+	 * @param c
+	 *            the Contact object representing the contact whose twins need
+	 *            be found. In the present implementation, only the following
+	 *            data matter:
+	 *            <UL>
+	 *            <LI>first name
+	 *            <LI>last name
+	 *            </UL>
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects) that may be empty but not null
+	 */
+	public List getTwinItems(Contact c) throws PIMDBAccessException {
+
+		log.info("\n\n=> PIMContactDAO getTwinItems begin\n");
+
+		List twins = new ArrayList();
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+
+		try {
+
+			String firstName = c.getName().getFirstName()
+					.getPropertyValueAsString();
+			String lastName = c.getName().getLastName()
+					.getPropertyValueAsString();
+
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			if (firstName == null || ("null".equals(firstName))) {
+				firstName = "";
+			}
+			if (lastName == null || ("null".equals(lastName))) {
+				lastName = "";
+			}
+
+			String fnSearch = (firstName.length() == 0 ? "<N/A>" : firstName
+					.toLowerCase());
+			String lnSearch = (lastName.length() == 0 ? "<N/A>" : lastName
+					.toLowerCase());
+			StringBuilder sb = new StringBuilder(100);
+
+			sb.append("\n\nLooking for items having: ").append(
+					"\n> first name   : '").append(fnSearch).append('\'')
+					.append("\n> last  name   : '").append(lnSearch).append(
+							'\'').append("\n");
+
+			log.info(sb.toString());
+
+			ps = con.prepareStatement(SQL_GET_CONTACT_TWIN_ID_LIST
+					+ SQL_ORDER_BY_ID);
+			ps.setLong(1, Long.parseLong(userId));
+			ps.setString(2, firstName.toLowerCase());
+			ps.setString(3, firstName.toLowerCase());
+			ps.setString(4, lastName.toLowerCase());
+			ps.setString(5, lastName.toLowerCase());
+			rs = ps.executeQuery();
+
+			long twinId;
+
+			while (rs.next()) {
+
+				twinId = rs.getLong(1); // The id is the first and only column
+				log.info("\n\n=> Twin found: " + twinId + "\n");
+				twins.add(Long.toString(twinId));
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException("\n=> Error retrieving twin.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		log.info("\n\n=> PIMContactDAO getTwinItems end\n");
+
+		return twins;
+	}
+
+	/**
+	 * Retrieves the state of the given item, provided it's been modified after
+	 * a certain moment.
+	 *
+	 * @param uid
+	 *            the UID of the item to be checked (as a String object)
+	 * @param since
+	 *            the Timestamp that the item's lastUpdate field is checked
+	 *            against: if the item has been modified before that moment, an
+	 *            "unchanged" state marker is returned
+	 * @throws PIMDBAccessException
+	 * @return a char identifying either one of the 3 standard states ("new",
+	 *         "deleted", "updated") or the special "unchanged" status, all of
+	 *         them as defined in com.funambol.pim.util.Def
+	 */
+	public char getItemState(String uid, Timestamp since)
+			throws PIMDBAccessException {
+
+		log.info("\n\n=> DAO start getItemState\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		char status;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			ps = con.prepareStatement(SQL_GET_STATUS_BY_ID_USER_TIME);
+			ps.setLong(1, Long.parseLong(uid));
+			ps.setLong(2, Long.parseLong(userId));
+			ps.setLong(3, since.getTime());
+			rs = ps.executeQuery();
+
+			if (!rs.next()) {
+
+				status = Def.PIM_STATE_UNCHANGED;
+
+				log.info("\n\n=> Item " + uid + "'s status wasn't retrieved "
+						+ "because the item hasn't been modified since "
+						+ since + "\n");
+
+			} else {
+
+				status = rs.getString(1).charAt(0);
+
+				log.info("\n\n=> Item " + uid + " has status " + status + "\n");
+
+			}
+
+		} catch (Exception e) {
+			throw new PIMDBAccessException(
+					"\n=> Error retrieving item state.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return status;
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	/**
+	 * Creates a ContactWrapper object from a ResultSet. Only the basic data are
+	 * set.
+	 *
+	 * @param wrapperId
+	 *            the UID of the wrapper object to be returned
+	 * @param rs
+	 *            the result of the execution of a proper SQL SELECT statement
+	 *            on the phpgw_cc_contact table, with the cursor before its
+	 *            first row
+	 * @return a newly created ContactWrapper initialized with the fields in the
+	 *         result set
+	 * @throws java.sql.SQLException
+	 * @throws NotFoundException
+	 */
+	private static ContactWrapper createContact(String wrapperId, ResultSet rs)
+			throws SQLException, NotFoundException {
+
+		if (!rs.next()) {
+			throw new NotFoundException("\n\n=> No contact found.\n");
+		}
+
+		ResultSetMetaData rsmd = rs.getMetaData();
+		ContactWrapper cw = null;
+
+		String column = null;
+		String uid = null;
+		String userId = null;
+
+		uid = String.valueOf(rs.getLong(SQL_FIELD_ID));
+		userId = rs.getString(SQL_FIELD_USERID);
+
+		Contact c = new Contact();
+		cw = new ContactWrapper(wrapperId, userId, c);
+
+		int columnCount = rsmd.getColumnCount();
+
+		for (int i = 1; i <= columnCount; ++i) {
+
+			column = rsmd.getColumnName(i);
+
+			// General
+			if (SQL_FIELD_ID.equalsIgnoreCase(column)) {
+				// Does nothing: field already set at construction time
+			} else if (SQL_FIELD_LAST_UPDATE.equalsIgnoreCase(column)) {
+				cw.setLastUpdate(new Timestamp(rs.getLong(i)));
+			} else if (SQL_FIELD_USERID.equalsIgnoreCase(column)) {
+				// Does nothing: field already set at construction time
+			} else if (SQL_FIELD_STATUS.equalsIgnoreCase(column)) {
+				cw.setStatus(rs.getString(i).charAt(0));
+			} else if (SQL_FIELD_FIRST_NAME.equalsIgnoreCase(column)) {
+				c.getName().getFirstName().setPropertyValue(rs.getString(i));
+			} else if (SQL_FIELD_LAST_NAME.equalsIgnoreCase(column)) {
+				c.getName().getLastName().setPropertyValue(rs.getString(i));
+			} else if (SQL_FIELD_BIRTHDAY.equalsIgnoreCase(column)) {
+				c.getPersonalDetail().setBirthday(rs.getString(i));
+			} else if (SQL_FIELD_CATEGORY.equalsIgnoreCase(column)) {
+				c.getCategories().setPropertyValue(rs.getString(i));
+			} else {
+				throw new SQLException("\n=> Unhandled column: " + column);
+			}
+		}
+
+		return cw;
+	}
+
+	/**
+	 * Attaches extra information to a contact on the basis of a ResultSet.
+	 *
+	 * @param c
+	 *            the contact still lacking extra information
+	 * @param rs
+	 *            the result of the execution of a proper SQL SELECT statement
+	 *            on the phpgw_cc_connections and phpgw_cc_contact_conns table,
+	 *            with the cursor before its first row
+	 * @return the ContactWrapper object with extra information attached
+	 * @throws java.sql.SQLException
+	 */
+	private static ContactWrapper addPIMContactItems(ContactWrapper cw,
+			ResultSet rs) throws SQLException {
+
+		ResultSetMetaData rsmd = rs.getMetaData();
+		Contact c = cw.getContact();
+
+		String type = null;
+		String value = null;
+		String name = null;
+		String column = null;
+
+		int columnCount = 0;
+
+		Phone phone;
+		Email email;
+
+		while (rs.next()) {
+
+			columnCount = rsmd.getColumnCount();
+
+			for (int i = 1; i <= columnCount; ++i) {
+
+				column = rsmd.getColumnName(i);
+
+				if (SQL_FIELD_ITEM_NAME.equalsIgnoreCase(column)) {
+					name = rs.getString(i);
+				} else if (SQL_FIELD_ITEM_VALUE.equalsIgnoreCase(column)) {
+					value = rs.getString(i);
+				} else if (SQL_FIELD_ITEM_TYPE.equalsIgnoreCase(column)) {
+					type = rs.getString(i);
+				} else {
+					throw new SQLException("\n=> Unhandled column: " + column);
+				}
+			}
+
+			if (type.equals("1")) { // email
+
+				if (TYPE_EMAIL_1_ADDRESS.equalsIgnoreCase(name)) {
+					email = new Email();
+					email.setEmailType(FIELD_EMAIL_1_ADDRESS);
+					email.setPropertyValue(value);
+					c.getPersonalDetail().addEmail(email);
+				} else if (TYPE_EMAIL_2_ADDRESS.equalsIgnoreCase(name)) {
+					email = new Email();
+					email.setEmailType(FIELD_EMAIL_2_ADDRESS);
+					email.setPropertyValue(value);
+					c.getPersonalDetail().addEmail(email);
+				}
+			}
+
+			if (type.equals("2")) { // telefone
+
+				if (TYPE_PRIMARY_TELEPHONE_NUMBER.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_PRIMARY_TELEPHONE_NUMBER);
+					phone.setPropertyValue(value);
+					c.getBusinessDetail().addPhone(phone);
+				} else if (TYPE_HOME_TELEPHONE_NUMBER.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_HOME_TELEPHONE_NUMBER);
+					phone.setPropertyValue(value);
+					c.getPersonalDetail().addPhone(phone);
+				} else if (TYPE_MOBILE_TELEPHONE_NUMBER.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_MOBILE_TELEPHONE_NUMBER);
+					phone.setPropertyValue(value);
+					c.getPersonalDetail().addPhone(phone);
+				} else if (TYPE_BUSINESS_TELEPHONE_NUMBER
+						.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_BUSINESS_TELEPHONE_NUMBER);
+					phone.setPropertyValue(value);
+					c.getBusinessDetail().addPhone(phone);
+				} else if (TYPE_BUSINESS_FAX_NUMBER.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_BUSINESS_FAX_NUMBER);
+					phone.setPropertyValue(value);
+					c.getBusinessDetail().addPhone(phone);
+				} else if (TYPE_PAGER_NUMBER.equalsIgnoreCase(name)) {
+					phone = new Phone();
+					phone.setPhoneType(FIELD_PAGER_NUMBER);
+					phone.setPropertyValue(value);
+					c.getBusinessDetail().addPhone(phone);
+				}
+			}
+		}
+
+		return cw;
+	}
+
+	/**
+	 * Retrieves the UID list of the contacts belonging to the user filtered
+	 * according to the given time interval and status.
+	 *
+	 * @param since
+	 *            the earliest allowed last-update Timestamp
+	 * @param to
+	 *            the latest allowed last-update Timestamp
+	 * @param status
+	 *            'D' for deleted items, 'N' for new items, 'U' for updated
+	 *            items
+	 * @throws PIMDBAccessException
+	 * @return a List of UIDs (as String objects)
+	 */
+	protected List getItemsHavingStatus(Timestamp since, Timestamp to,
+			char status) throws PIMDBAccessException {
+
+		log.info("\n\n=> Seeking '" + status + "' items "
+				+ "in time interval (" + since + "; " + to + ")\n");
+
+		Connection con = null;
+		PreparedStatement ps = null;
+		List contacts = new ArrayList();
+		ResultSet rs = null;
+
+		try {
+			// Looks up the data source when the first connection is created
+			con = getDataSource().getConnection();
+
+			if (status == 'D') {
+
+				ps = con
+						.prepareStatement("SELECT guid FROM fnbl_client_mapping WHERE sync_source = 'catalogo' AND guid NOT IN (SELECT id_contact FROM phpgw_cc_contact WHERE id_owner = ?) AND principal = ? ORDER BY guid");
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setLong(2, this.principal.getId());
+				rs = ps.executeQuery();
+
+			} else {
+
+				ps = con
+						.prepareStatement(SQL_GET_FNBL_PIM_CONTACT_ID_LIST_BY_USER_TIME_STATUS
+								+ SQL_ORDER_BY_ID);
+				ps.setLong(1, Long.parseLong(userId));
+				ps.setLong(2, since.getTime());
+				ps.setLong(3, to.getTime());
+				ps.setString(4, String.valueOf(status));
+				rs = ps.executeQuery();
+			}
+
+			while (rs.next()) {
+				contacts.add(Long.toString(rs.getLong(1))); // It's the first
+				// and only column
+				log.info("\n\n=> Item found " + rs.getLong(1) + "\n");
+			}
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new PIMDBAccessException("\n=> Error listing contacts.\n", e);
+		} finally {
+			DBTools.close(con, ps, rs);
+		}
+
+		return contacts;
+	}
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/manager/PIMCalendarManager.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/manager/PIMCalendarManager.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/manager/PIMCalendarManager.java	(revision 2082)
@@ -0,0 +1,163 @@
+/**
+ *
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.items.manager;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+import br.com.prognus.psync.exception.EntityException;
+import br.com.prognus.psync.exception.PIMDBAccessException;
+import br.com.prognus.psync.items.dao.PIMCalendarDAO;
+import br.com.prognus.psync.items.dao.PIMEntityDAO;
+import br.com.prognus.psync.items.model.CalendarWrapper;
+
+import com.funambol.common.pim.calendar.Calendar;
+import com.funambol.framework.security.Sync4jPrincipal;
+import com.funambol.framework.tools.merge.MergeResult;
+
+public class PIMCalendarManager extends PIMEntityManager {
+
+	// ------------------------------------------------------------- Private
+	// data
+
+	private PIMCalendarDAO dao;
+
+	// -------------------------------------------------------------
+	// Constructors
+
+	/**
+	 * @see PIMEntityDAO#PIMEntityDAO(String, String)
+	 */
+	public PIMCalendarManager(String jndiDataSourceName,
+			Sync4jPrincipal principal) {
+
+		this.dao = new PIMCalendarDAO(jndiDataSourceName, principal);
+		super.dao = this.dao;
+
+		log.trace("Created new PIMCalendarManager for user ID "
+				+ principal.getUsername());
+	}
+
+	// ----------------------------------------------------------- Public
+	// methods
+
+	/**
+	 * Updates a calendar.
+	 * 
+	 * @param uid
+	 *            the UID of the calendar
+	 * @param c
+	 *            a Calendar object
+	 * @param t
+	 *            the last update time that will be forced as the last update
+	 *            time of the updated calendar
+	 * @return the UID of the calendar (it should be the same as the argument)
+	 * @throws EntityException
+	 */
+	public String updateItem(String uid, Calendar c, Timestamp t)
+			throws EntityException {
+
+		log.trace("Updating calendar with ID " + uid
+				+ " on the server, setting " + t
+				+ " as its last update timestamp.");
+
+		CalendarWrapper cw = new CalendarWrapper(uid, this.dao.getUserId(), c);
+		cw.setLastUpdate(t);
+
+		try {
+			this.dao.updateItem(cw);
+			return cw.getId();
+		} catch (Exception e) {
+			throw new EntityException("Error updating item.", e);
+		}
+	}
+
+	/**
+	 * @see PIMCalendarDAO#addItem(Calendar)
+	 */
+	public String addItem(Calendar c, Timestamp t) throws EntityException {
+
+		try {
+			CalendarWrapper cw = createCalendarWrapper(c);
+			cw.setLastUpdate(t);
+			this.dao.addItem(cw);
+			return cw.getId();
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error adding item.", dbae);
+		}
+	}
+
+	/**
+	 * @see PIMCalendarDAO#getItem(String)
+	 */
+	public CalendarWrapper getItem(String id) throws EntityException {
+		try {
+			return this.dao.getItem(id);
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error getting item. " + dbae);
+		}
+	}
+
+	/**
+	 * @see PIMCalendarDAO#getTwinItems(Calendar)
+	 */
+	public List getTwins(Calendar c) throws EntityException {
+		try {
+			return this.dao.getTwinItems(c);
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error getting twins of an item.", dbae);
+		}
+	}
+
+	public boolean mergeItems(String serverCalendarID, Calendar clientCalendar,
+			Timestamp t) throws EntityException {
+
+		log.info("\n\n=> PIMCalendarManager mergeItems begin");
+		log.info("\n\n=> Merging server item " + serverCalendarID
+				+ " with its client counterpart.");
+
+		try {
+			CalendarWrapper serverCalendarWrapper = this.dao
+					.getItem(serverCalendarID);
+			Calendar serverCalendar = serverCalendarWrapper.getCalendar();
+			MergeResult mergeResult = clientCalendar.merge(serverCalendar);
+			if (mergeResult.isSetBRequired()) {
+				updateItem(serverCalendarID, serverCalendar, t);
+			}
+
+			log.info("MergeResult: " + mergeResult);
+
+			return mergeResult.isSetARequired();
+
+		} catch (Exception e) {
+
+			log.error("Error merging items.", e);
+			throw new EntityException("Error merging items. " + e);
+		}
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	private CalendarWrapper createCalendarWrapper(Calendar c) {
+
+		log.info("Created a new CalendarWrapper (UID not yet generated).");
+		return new CalendarWrapper(null, this.dao.getUserId(), c);
+	}
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/manager/PIMContactManager.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/manager/PIMContactManager.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/manager/PIMContactManager.java	(revision 2082)
@@ -0,0 +1,177 @@
+/**
+ *
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.items.manager;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+import br.com.prognus.psync.exception.EntityException;
+import br.com.prognus.psync.exception.PIMDBAccessException;
+import br.com.prognus.psync.items.dao.PIMContactDAO;
+import br.com.prognus.psync.items.dao.PIMEntityDAO;
+import br.com.prognus.psync.items.model.ContactWrapper;
+
+import com.funambol.common.pim.contact.Contact;
+import com.funambol.framework.security.Sync4jPrincipal;
+import com.funambol.framework.tools.merge.MergeResult;
+
+public class PIMContactManager extends PIMEntityManager {
+
+	// ------------------------------------------------------------- Private
+	// data
+
+	private PIMContactDAO dao;
+
+	// -------------------------------------------------------------
+	// Constructors
+
+	/**
+	 * @see PIMEntityDAO#PIMEntityDAO(String, String)
+	 */
+	public PIMContactManager(String jndiDataSourceName,
+			Sync4jPrincipal principal) {
+
+		this.dao = new PIMContactDAO(jndiDataSourceName, principal);
+		super.dao = this.dao;
+
+		if (log.isTraceEnabled()) {
+			log.trace("Created new PIMContactManager for user ID "
+					+ principal.getUsername() + "\n");
+		}
+
+	}
+
+	// ----------------------------------------------------------- Public
+	// methods
+
+	/**
+	 * Updates a contact.
+	 * 
+	 * @param uid
+	 *            the UID of the contact
+	 * @param c
+	 *            a Contact object
+	 * @param t
+	 *            the last update time that will be forced as the last update
+	 *            time of the updated contact
+	 * @return the UID of the contact (it should be the same as the argument)
+	 * @throws EntityException
+	 */
+	public String updateItem(String uid, Contact c, Timestamp t)
+			throws EntityException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("Updating contact with ID " + uid
+					+ " on the server, setting " + t
+					+ " as its last update timestamp.");
+		}
+
+		ContactWrapper cw = new ContactWrapper(uid, this.dao.getUserId(), c);
+		cw.setLastUpdate(t);
+
+		try {
+			return this.dao.updateItem(cw);
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error updating item.", dbae);
+		}
+	}
+
+	/**
+	 * @see PIMContactDAO#addItem(Contact)
+	 */
+	public String addItem(Contact c, Timestamp t) throws EntityException {
+
+		try {
+			ContactWrapper cw = createContactWrapper(c);
+			cw.setLastUpdate(t);
+			this.dao.addItem(cw);
+			return cw.getId();
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error adding item.", dbae);
+		}
+	}
+
+	/**
+	 * @see PIMContactDAO#getItem(String)
+	 */
+	public ContactWrapper getItem(String id) throws EntityException {
+		try {
+			return this.dao.getItem(id);
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error getting item. " + dbae);
+		}
+	}
+
+	/**
+	 * @see PIMContactDAO#getTwinItems(Contact)
+	 */
+	public List getTwins(Contact c) throws EntityException {
+		try {
+			return this.dao.getTwinItems(c);
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error getting twins of an item.", dbae);
+		}
+	}
+
+	public boolean mergeItems(String serverContactID, Contact clientContact,
+			Timestamp t) throws EntityException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("PIMContactManager mergeItems begin");
+			log.trace("Merging server item " + serverContactID
+					+ " with its client counterpart.");
+		}
+
+		try {
+
+			ContactWrapper serverContactWrapper = this.dao
+					.getItem(serverContactID);
+			Contact serverContact = serverContactWrapper.getContact();
+			MergeResult mergeResult = clientContact.merge(serverContact);
+
+			if (mergeResult.isSetBRequired()) {
+				updateItem(serverContactID, serverContact, t);
+			}
+
+			if (log.isTraceEnabled()) {
+				log.trace("MergeResult: " + mergeResult);
+			}
+
+			return mergeResult.isSetARequired();
+
+		} catch (Exception e) {
+
+			log.error("SyncSource error merging the items", e);
+			throw new EntityException("Error merging items. " + e);
+		}
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	private ContactWrapper createContactWrapper(Contact c) {
+
+		if (log.isTraceEnabled()) {
+			log.trace("Created a new ContactWrapper (UID not yet generated).");
+		}
+
+		return new ContactWrapper(null, this.dao.getUserId(), c);
+	}
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/manager/PIMEntityManager.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/manager/PIMEntityManager.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/items/manager/PIMEntityManager.java	(revision 2082)
@@ -0,0 +1,110 @@
+/**
+ *
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.items.manager;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+import br.com.prognus.psync.exception.EntityException;
+import br.com.prognus.psync.exception.PIMDBAccessException;
+import br.com.prognus.psync.items.dao.PIMEntityDAO;
+import br.com.prognus.psync.util.Def;
+
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+
+public abstract class PIMEntityManager {
+
+	// ------------------------------------------------------------ Private data
+
+	protected static final FunambolLogger log = FunambolLoggerFactory
+			.getLogger(Def.LOGGER_NAME);
+
+	protected PIMEntityDAO dao;
+
+	// ---------------------------------------------------------- Public methods
+
+	public char getItemState(String id, Timestamp t) throws EntityException {
+		try {
+			return dao.getItemState(id, t);
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error while getting item state.", dbae);
+		}
+	}
+
+	public List getAllItems() throws EntityException {
+
+		try {
+			return dao.getAllItems();
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error while getting all items.", dbae);
+		}
+	}
+
+	public List getNewItems(Timestamp since, Timestamp to)
+			throws EntityException {
+
+		try {
+			return dao.getNewItems(since, to);
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error while getting new items.", dbae);
+		}
+	}
+
+	public List getUpdatedItems(Timestamp since, Timestamp to)
+			throws EntityException {
+
+		try {
+			return dao.getUpdatedItems(since, to);
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error while getting updated items.",
+					dbae);
+		}
+	}
+
+// Correcao - Incluido parametro sourceURI - emerson-faria.nobre@serpro.gov.br - 17/set/2009
+	public List getDeletedItems(Timestamp since, Timestamp to, String sourceURI)
+			throws EntityException {
+
+		try {
+			return dao.getDeletedItems(since, to, sourceURI);
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error while getting deleted items.",
+					dbae);
+		}
+	}
+
+	public void removeItem(String uid) throws EntityException {
+
+		try {
+			dao.removeItem(uid);
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error while deleting item.", dbae);
+		}
+	}
+
+	public void removeAllItems() throws EntityException {
+
+		try {
+			dao.removeAllItems();
+		} catch (PIMDBAccessException dbae) {
+			throw new EntityException("Error while removing all items.", dbae);
+		}
+	}
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/BeanShellSynclet.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/BeanShellSynclet.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/BeanShellSynclet.java	(revision 2082)
@@ -0,0 +1,415 @@
+/**
+ * Copyright (C) 2005-2007 Funambol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Honest Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Honest Public License for more details.
+ *
+ * You should have received a copy of the Honest Public License
+ * along with this program; if not, write to Funambol,
+ * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
+ */
+package br.com.prognus.psync.synclet;
+
+import java.io.*;
+
+import java.net.URL;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import bsh.BshClassManager;
+import bsh.Interpreter;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+import com.funambol.framework.config.ConfigClassLoader;
+import com.funambol.framework.core.Sync4jException;
+import com.funambol.framework.core.SyncML;
+import com.funambol.framework.engine.pipeline.*;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+
+import com.funambol.server.config.Configuration;
+
+/**
+ * Goal of the BeanShell synclet is to provide a way to develop synclets without
+ * the need to follow the build/pack/deploy cycle at any change. This will also
+ * minimize the downtime of a running server and allows to apply quick hot
+ * fixes. To achieve this, BeanShell synclet is based on the use of a scripting
+ * language which is interpreted instead of compiled. The BeanShell scripting
+ * language is the scripting used by this synclet, since it is very similar to
+ * Java.
+ *
+ * @version $Id: BeanShellSynclet.java,v 1.5 2007-04-13 08:51:02 luigiafassina Exp $
+ */
+public class BeanShellSynclet
+implements InputMessageProcessor, OutputMessageProcessor {
+
+    // ------------------------------------------------------------- Static data
+
+    /**
+     * The interpreters hash map
+     */
+    private static HashMap interpreters;
+
+    /**
+     * Logger
+     */
+    private static final FunambolLogger log =
+        FunambolLoggerFactory.getLogger("engine");
+
+    // ------------------------------------------------------------ Private data
+
+    /**
+     * The script name
+     */
+    private String script;
+
+    /**
+     * The HTTP user agent pattern to match in order to trigger execution
+     */
+    private String pattern;
+
+    /**
+     * HTTP header the pattern is applied to
+     */
+    private String header;
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * Delegates the call to the script.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     *
+     * @throws Sync4jException
+     */
+    public void preProcessMessage(MessageProcessingContext processingContext,
+                                  SyncML                   message)
+    throws Sync4jException {
+        if (!isDeviceToProcess(processingContext)) {
+            return;
+        }
+
+        try {
+            InputMessageProcessor imp = getInputSynclet();
+            imp.preProcessMessage(processingContext, message);
+        } catch (StopProcessingException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("Error in processing the input message", e);
+        }
+    }
+
+    /**
+     * Delegates the call to the script.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     *
+     * @throws Sync4jException
+     */
+    public void postProcessMessage(MessageProcessingContext processingContext,
+                                   SyncML                   message)
+    throws Sync4jException {
+        if (!isDeviceToProcess(processingContext)) {
+            return;
+        }
+
+        try {
+            OutputMessageProcessor omp = getOutputSynclet();
+            omp.postProcessMessage(processingContext, message);
+        } catch (StopProcessingException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("Error in processing the output message", e);
+        }
+    }
+
+    // --------------------------------------------------------------- Accessors
+
+    /**
+     * Sets script
+     *
+     * @param script the script name
+     */
+    public void setScript(String script) {
+        this.script = script;
+    }
+
+    /**
+     * Returns script
+     *
+     * @return scripts
+     */
+    public String getScript() {
+        return this.script;
+    }
+
+    public String getHeader() {
+        return header;
+    }
+
+    public void setHeader(String header) {
+        this.header = header;
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+    }
+
+    public String toString() {
+        ToStringBuilder sb = new ToStringBuilder(this);
+
+        sb.append("script",  script ).
+           append("header",  header ).
+           append("pattern", pattern);
+
+        return sb.toString();
+    }
+    // --------------------------------------------------------- Private methods
+
+    /**
+     * Returns the interpreter associated to the instance script name. If an
+     * interpreter instance is not yet created, it is created before being
+     * returned.
+     * It also checks if the last modification timestamp of the script is more
+     * recent that the last read. If so, the new script is loaded in the
+     * interpreter.
+     *
+     * @return the interpreter instance associated to the instance's script
+     */
+    private BeanShellEntry getBeanShellEntry() {
+        assert (script != null && script.trim().length()>0);
+
+        BeanShellEntry bse = null;
+        synchronized (this) {
+            bse = (BeanShellEntry)interpreters.get(script);
+
+            if (bse == null) {
+                bse = new BeanShellEntry();
+                bse.lastModified = -1;
+
+                interpreters.put(script, bse);
+            }
+        }
+
+        return bse;
+    }
+
+    /**
+     * Creates and return an Interpreter. Plus, it sets adds the config class
+     * loader to the interpreter class loader.
+     *
+     * @return the created interpreter
+     */
+    private Interpreter createInterpreter() {
+        Interpreter interpreter = new Interpreter();
+
+        ConfigClassLoader cl =
+            (ConfigClassLoader)Configuration.getConfiguration().getClassLoader();
+
+        BshClassManager cm = interpreter.getClassManager();
+        URL[] urls = cl.getURLs();
+        for (int i=0; i<urls.length; ++i) {
+            try {
+                cm.addClassPath(urls[i]);
+            } catch (IOException e) {
+                log.error( "Unable to add '"
+                          + urls[i]
+                          + "' to the classpath.", e);
+            }
+        }
+
+        return interpreter;
+    }
+
+    /**
+     * Returns the scripted MessageInputProcessor
+     *
+     * @return the scripted MessageInputProcessor
+     */
+    private InputMessageProcessor getInputSynclet()
+    throws Sync4jException {
+
+        BeanShellEntry bse = getBeanShellEntry();
+
+        //
+        // Checking if the script is more recent that the one interpreted
+        //
+        File scriptFile = getScriptFile();
+
+        if (bse.lastModified < scriptFile.lastModified()) {
+            try {
+                Interpreter interpreter = createInterpreter();
+                bse.inputSynclet = evalInputScript(interpreter);
+                bse.lastModified = scriptFile.lastModified();
+                evalInitMethod(interpreter);
+            } catch (Exception e) {
+                throw new Sync4jException(e);
+            }
+        }
+
+        return bse.inputSynclet;
+    }
+
+    /**
+     * Returns the scripted OutputMessageProcessor
+     *
+     * @return the scripted OutputMessageProcessor
+     */
+    private OutputMessageProcessor getOutputSynclet()
+    throws Sync4jException {
+        BeanShellEntry bse = getBeanShellEntry();
+
+        //
+        // Checking if the script is more recent that the one interpreted
+        //
+        File scriptFile = getScriptFile();
+
+        if (bse.lastModified < scriptFile.lastModified()) {
+            try {
+                Interpreter interpreter = createInterpreter();
+                bse.outputSynclet = evalOutputScript(interpreter);
+                bse.lastModified = scriptFile.lastModified();
+                evalInitMethod(interpreter);
+            } catch (Exception e) {
+                throw new Sync4jException(e);
+            }
+        }
+
+        return bse.outputSynclet;
+    }
+
+    /**
+     * Evaluates the script in the given interpreter and returns the results
+     * of the eveluation. In order to make it to return an InputMessageProcessor
+     * the command "return (InputMessageProcessor)this" is appended to the
+     * script.
+     *
+     * @param interpreter the interpreter into which evaluate the script
+     */
+    private InputMessageProcessor evalInputScript(Interpreter interpreter)
+    throws Exception {
+        FileReader r = new FileReader(getScriptFile());
+
+        interpreter.eval(r);
+        return (InputMessageProcessor)interpreter.eval(";\nreturn (InputMessageProcessor)this");
+    }
+
+    /**
+     * Evaluates the script in the given interpreter and returns the results
+     * of the eveluation. In order to make it to return an OutputMessageProcessor
+     * the command "return (OutputMessageProcessor)this" is appended to the
+     * script.
+     *
+     * @param interpreter the interpreter into which evaluate the script
+     */
+    private OutputMessageProcessor evalOutputScript(Interpreter interpreter)
+    throws Exception {
+        FileReader r = new FileReader(getScriptFile());
+
+        interpreter.eval(r);
+        return (OutputMessageProcessor)interpreter.eval(";\nreturn (OutputMessageProcessor)this");
+    }
+
+    /**
+     * Returns the script file as a File object, which is the combination of the
+     * configpath and the value of the script property.
+     *
+     * @return the script file object
+     */
+    private File getScriptFile() {
+        assert (script != null && script.length()>0);
+        return new File(Configuration.getConfiguration().getConfigPath(), script);
+    }
+
+    /**
+     * Searches a match for the configured <code>pattern</code> with the HTTP
+     * header specified by <code>header</code>.
+     * If header or pattern is empty, isDeviceToProcess() returns always true.
+     * If the client does not provide the specified header, isDeviceToProcess()
+     * returns false.
+     *
+     * @param context message processing context
+     *
+     * @return true if the device must be proced by this synclet, false otherwise
+     */
+    private boolean isDeviceToProcess(MessageProcessingContext context) {
+        if ((pattern == null || pattern.trim().length() == 0)
+        || (header  == null || header.trim().length()  == 0)) {
+            return true;
+        }
+
+        Map headers =
+            (Map)context.getRequestProperty(context.PROPERTY_REQUEST_HEADERS);
+
+        //
+        // Search for the requested header value
+        //
+        String value = null;
+        Iterator i = headers.keySet().iterator();
+        while (i.hasNext()) {
+            String h = (String)i.next();
+            if (header.equalsIgnoreCase(h)) {
+                value = (String)headers.get(h);
+                break;
+            }
+        }
+
+        if ((value == null) || (value.trim().length() == 0)) {
+            //
+            // Header not found or not specified
+            //
+            return false;
+        }
+
+        Pattern p = Pattern.compile(pattern);
+        Matcher m = p.matcher(value);
+
+        return m.find();
+    }
+
+    /**
+     * Evaluates the init() method
+     *
+     * @param interpreter the interpreter into which evaluate the script
+     */
+    private void evalInitMethod(Interpreter interpreter) {
+        try {
+            interpreter.eval("init()");
+        } catch(Exception e) {
+            log.error("Error calling the init method", e);
+        }
+    }
+
+    // --------------------------------------------------- Static initialization
+
+    static {
+        interpreters = new HashMap();
+    }
+
+    // ----------------------------------------------------------- Inner classes
+
+    /**
+     * This class represent the key to use to put the interpreters instances
+     * into the hash map. It redefines hashCode() and equals() to the String
+     * script name's ones.
+     */
+    private class BeanShellEntry {
+        public long lastModified;
+        public InputMessageProcessor inputSynclet;
+        public OutputMessageProcessor outputSynclet;
+    }
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/SourceUriPrefixSynclet.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/SourceUriPrefixSynclet.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/SourceUriPrefixSynclet.java	(revision 2082)
@@ -0,0 +1,521 @@
+/**
+ * Copyright (C) 2006-2007 Funambol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Honest Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Honest Public License for more details.
+ *
+ * You should have received a copy of the Honest Public License
+ * along with this program; if not, write to Funambol,
+ * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
+ */
+package br.com.prognus.psync.synclet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import com.funambol.framework.core.AbstractCommand;
+import com.funambol.framework.core.Alert;
+import com.funambol.framework.core.Status;
+import com.funambol.framework.core.Results;
+import com.funambol.framework.core.Sync;
+import com.funambol.framework.core.Map;
+import com.funambol.framework.core.Item;
+import com.funambol.framework.core.DataStore;
+import com.funambol.framework.core.Source;
+import com.funambol.framework.core.Sync4jException;
+import com.funambol.framework.core.SyncBody;
+import com.funambol.framework.core.SyncML;
+import com.funambol.framework.core.Target;
+import com.funambol.framework.core.TargetRef;
+import com.funambol.framework.core.SourceRef;
+import com.funambol.framework.engine.pipeline.InputMessageProcessor;
+import com.funambol.framework.engine.pipeline.OutputMessageProcessor;
+import com.funambol.framework.engine.pipeline.MessageProcessingContext;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+import com.funambol.framework.protocol.ProtocolUtil;
+import com.funambol.framework.core.DevInfItem;
+
+/**
+ * This class strips the source URIs with a './' if needed.
+ *
+ * This is done in order to allow any client to sync with the source URIs
+ * defined, because some devices have a prefix of './'.
+ *
+ * @version $Id: SourceUriPrefixSynclet.java,v 1.4 2007-02-15 10:23:46 luigiafassina Exp $
+ */
+public class SourceUriPrefixSynclet
+implements InputMessageProcessor, OutputMessageProcessor {
+
+    // --------------------------------------------------------------- Constants
+    private static final String SYNCLET_NAME =
+        SourceUriPrefixSynclet.class.getName();
+
+    private static final String PROPERTY_SOURCENAME_CHANGED =
+        "funambol.foundation.sourceuriprefix.SOURCENAME_CHANGED";
+    // ------------------------------------------------------------ Private data
+    private static final FunambolLogger log =
+            FunambolLoggerFactory.getLogger("engine.pipeline");
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * Process input message and set MessageProcessingContext property.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     * @throws Sync4jException
+     */
+    public void preProcessMessage(MessageProcessingContext processingContext,
+                                  SyncML message                            )
+    throws Sync4jException {
+
+        if (log.isTraceEnabled()) {
+            log.trace(SYNCLET_NAME + ".preProcessMessage(...)");
+        }
+
+        HashMap sourceReplacedMap =
+            (HashMap)processingContext.getSessionProperty(
+                                                   PROPERTY_SOURCENAME_CHANGED);
+
+        if (sourceReplacedMap == null) {
+            sourceReplacedMap = new HashMap();
+            processingContext.setSessionProperty(PROPERTY_SOURCENAME_CHANGED,
+                                                 sourceReplacedMap          );
+        }
+
+        //
+        // Store and change the source uri into commands
+        //
+        manageInputAlert (message, sourceReplacedMap);
+        manageInputStatus(message, sourceReplacedMap);
+        manageInputSync  (message, sourceReplacedMap);
+        manageInputMap   (message, sourceReplacedMap);
+
+    }
+
+    /**
+     * Process and manipulate the output message.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     * @throws Sync4jException
+     */
+    public void postProcessMessage(MessageProcessingContext processingContext,
+                                   SyncML message) throws Sync4jException {
+        if (log.isTraceEnabled()) {
+            log.trace(SYNCLET_NAME + ".postProcessMessage(...)");
+        }
+
+        HashMap sourceReplacedMap =
+            (HashMap)processingContext.getSessionProperty(
+                                                   PROPERTY_SOURCENAME_CHANGED);
+        if (!sourceReplacedMap.isEmpty()) {
+            //
+            // Replace original source uri into commands
+            //
+            manageOutputStatus (message, sourceReplacedMap);
+            manageOutputResults(message, sourceReplacedMap);
+            manageOutputAlert  (message, sourceReplacedMap);
+            manageOutputSync   (message, sourceReplacedMap);
+       }
+    }
+
+    // --------------------------------------------------------- Private Methods
+
+    /**
+     * Store and change Target URI into Alert commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap in which store uri to replace
+     */
+    private void manageInputAlert(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Strip TargetURI into input Alert commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allClientCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList alertList =
+            ProtocolUtil.filterCommands(allClientCommands, Alert.class);
+
+        Iterator itAlertList = alertList.iterator();
+
+        Alert alert = null;
+        ArrayList items = null;
+        Iterator itItem = null;
+        Item item = null;
+        Target target = null;
+        String targetUri = null;
+
+        while (itAlertList.hasNext()) {
+            alert = (Alert)itAlertList.next();
+            items = alert.getItems();
+            itItem = items.iterator();
+            while (itItem.hasNext()) {
+
+                item = (Item)itItem.next();
+                target = item.getTarget();
+                if (target != null) {
+                    targetUri = target.getLocURI();
+                    if (log.isTraceEnabled()) {
+                        log.trace("original targetUri: " + targetUri);
+                    }
+
+                    if (targetUri.startsWith("./")) {
+                        String targetNew = targetUri.substring(2);
+                        if (log.isTraceEnabled()) {
+                            log.trace("new targetUri: " + targetNew);
+                        }
+                        target.setLocURI(targetNew);
+
+                        sourceReplacedMap.put(targetNew, targetUri);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Store and change SourceRef into Status commmads
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap in which store uri to replace
+     */
+    private void manageInputStatus(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Strip SourceRef into input Status commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allClientCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList statusList =
+            ProtocolUtil.filterCommands(allClientCommands, Status.class);
+
+        Iterator itStatusList = statusList.iterator();
+
+        Status status = null;
+        while (itStatusList.hasNext()) {
+            status = (Status)itStatusList.next();
+            SourceRef[] srefs =
+                (SourceRef[])status.getSourceRef().toArray(new SourceRef[0]);
+            int s = srefs.length;
+            for(int i=0;i<s;i++) {
+                SourceRef sr = (SourceRef)srefs[i];
+                String sourceRef = sr.getValue();
+                if (log.isTraceEnabled()) {
+                    log.trace("original sourceRef: " + sourceRef);
+                }
+
+                if (sourceRef.startsWith("./")) {
+                    String sourceRefNew = sourceRef.substring(2);
+                    if (log.isTraceEnabled()) {
+                        log.trace("new sourceRef: " + sourceRefNew);
+                    }
+                    sr.setValue(sourceRefNew);
+
+                    sourceReplacedMap.put(sourceRefNew, sourceRef);
+                }
+            }
+        }
+    }
+
+    /**
+     * Store and change Target into Sync commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap in which store uri to replace
+     */
+    private void manageInputSync(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Strip TargetURI into input Sync commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allClientCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList syncList =
+            ProtocolUtil.filterCommands(allClientCommands, Sync.class);
+
+        Iterator itList = syncList.iterator();
+
+        Sync sync = null;
+        String targetUri = null;
+
+        while (itList.hasNext()) {
+            sync = (Sync)itList.next();
+            targetUri = sync.getTarget().getLocURI();
+            if (log.isTraceEnabled()) {
+                log.trace("original targetUri: " + targetUri);
+            }
+
+            if (targetUri.startsWith("./")) {
+                String targetNew = targetUri.substring(2);
+                if (log.isTraceEnabled()) {
+                    log.trace("new targetUri: " + targetNew);
+                }
+                sync.getTarget().setLocURI(targetNew);
+
+                sourceReplacedMap.put(targetNew, targetUri);
+            }
+        }
+    }
+
+    /**
+     * Store and change Target into Map commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap in which store uri to replace
+     */
+    private void manageInputMap(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Strip TargetURI into input Map commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allClientCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList mapList =
+            ProtocolUtil.filterCommands(allClientCommands, Map.class);
+
+        Iterator itList = mapList.iterator();
+
+        Map map = null;
+        String targetUri = null;
+
+        while (itList.hasNext()) {
+            map = (Map)itList.next();
+            targetUri = map.getTarget().getLocURI();
+            if (log.isTraceEnabled()) {
+                log.trace("original targetUri: " + targetUri);
+            }
+
+            if (targetUri.startsWith("./")) {
+                String targetNew = targetUri.substring(2);
+
+                if (log.isTraceEnabled()) {
+                    log.trace("new targetUri: " + targetNew);
+                }
+                map.getTarget().setLocURI(targetNew);
+
+                sourceReplacedMap.put(targetNew, targetUri);
+            }
+        }
+    }
+
+    /**
+     * Replace TargetRef into Status commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap with the uri to replace
+     */
+    private void manageOutputStatus(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Replace TargetRef into output Status commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allServerCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList statusList =
+            ProtocolUtil.filterCommands(allServerCommands, Status.class);
+
+        Iterator itStatusList = statusList.iterator();
+
+        Status status = null;
+        while (itStatusList.hasNext()) {
+            status = (Status)itStatusList.next();
+
+            if (status.getTargetRef() == null) {
+                continue;
+            }
+            TargetRef[] trefs =
+                (TargetRef[])status.getTargetRef().toArray(new TargetRef[0]);
+            int s = trefs.length;
+            for(int i=0;i<s;i++) {
+                TargetRef tr = (TargetRef)trefs[i];
+                if (sourceReplacedMap.containsKey(tr.getValue())) {
+                    tr.setValue((String)sourceReplacedMap.get(tr.getValue()));
+                }
+            }
+        }
+    }
+
+    /**
+     * Replace Source into Alert commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap with the uri to replace
+     */
+    private void manageOutputAlert(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Replace Source into output Alert commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allServerCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList alertList =
+            ProtocolUtil.filterCommands(allServerCommands, Alert.class);
+
+        Iterator itAlertList = alertList.iterator();
+
+        Alert alert = null;
+        ArrayList items = null;
+        Iterator itItem = null;
+        Item item = null;
+        Source source = null;
+        String sourceUri = null;
+
+        while (itAlertList.hasNext()) {
+            alert = (Alert)itAlertList.next();
+            items = alert.getItems();
+            itItem = items.iterator();
+            while (itItem.hasNext()) {
+
+                item = (Item)itItem.next();
+                source = item.getSource();
+                if (source != null) {
+                    sourceUri = source.getLocURI();
+
+                    if (sourceReplacedMap.containsKey(sourceUri)) {
+                        //
+                        // Here we have to create a new Source object because
+                        // the Alert can contain the same Source used in the
+                        // engine/sessionHandler to handle the Database.
+                        // If here we change the source object of the alert we
+                        // change also the source of the database because it's
+                        // the same object!!.
+                        //
+                        //
+                        Source newSource = new Source(
+                            (String)sourceReplacedMap.get(sourceUri),
+                            source.getLocName()
+                        );
+                        item.setSource(newSource);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Replace SourceRef into Results command
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap with the uri to replace
+     */
+    private void manageOutputResults(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Replace SourceRef into output Result commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allServerCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        List list =
+            ProtocolUtil.filterCommands(allServerCommands, Results.class);
+
+        if (list.isEmpty()) {
+            return;
+        }
+
+        Results results = (Results)list.get(0);
+
+        Item[] items = (Item[])results.getItems().toArray(new Item[0]);
+
+        if (items != null && items.length > 0) {
+            if (items[0] instanceof DevInfItem) {
+                DevInfItem item = (DevInfItem) items[0];
+                ArrayList dss = item.getDevInfData().getDevInf().getDataStores();
+                int s = dss.size();
+                for (int i = 0; i < s; i++) {
+                    DataStore ds = (DataStore) dss.get(i);
+                    String sourceRef = ds.getSourceRef().getValue();
+                    if (sourceReplacedMap.containsKey(sourceRef)) {
+                        ds.getSourceRef().setValue((String) sourceReplacedMap.
+                                get(sourceRef));
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Replace Source into Sync commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap with the uri to replace
+     */
+    private void manageOutputSync(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Replace Source into output Sync commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allServerCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList syncList =
+            ProtocolUtil.filterCommands(allServerCommands, Sync.class);
+
+        Iterator itSyncList = syncList.iterator();
+
+        Sync sync        = null;
+        Source source    = null;
+        String sourceUri = null;
+
+        while (itSyncList.hasNext()) {
+            sync = (Sync) itSyncList.next();
+
+            source = sync.getSource();
+
+            if (source != null) {
+                sourceUri = source.getLocURI();
+
+                if (sourceReplacedMap.containsKey(sourceUri)) {
+                    //
+                    // Here we have to create a new Source object because
+                    // the Sync can contain the same Source used in the
+                    // engine/sessionHandler to handle the Database.
+                    // If here we change the source object of the sync commands we
+                    // change also the source of the database because it's
+                    // the same object!!.
+                    //
+                    //
+                    Source newSource = new Source(
+                            (String) sourceReplacedMap.get(sourceUri),
+                            source.getLocName()
+                                       );
+                    sync.setSource(newSource);
+                }
+            }
+        }
+    }
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/ChangeDeviceIdSynclet.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/ChangeDeviceIdSynclet.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/ChangeDeviceIdSynclet.java	(revision 2082)
@@ -0,0 +1,261 @@
+/**
+ * Copyright (C) 2003-2007 Funambol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Honest Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Honest Public License for more details.
+ *
+ * You should have received a copy of the Honest Public License
+ * along with this program; if not, write to Funambol,
+ * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
+ */
+package br.com.prognus.psync.synclet;
+
+import java.util.*;
+
+import com.funambol.framework.core.*;
+import com.funambol.framework.engine.pipeline.*;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+import com.funambol.framework.protocol.ProtocolUtil;
+
+/**
+ * This class changes the input message client deviceId with a fixed deviceId.
+ * This is done in order to allow any phone to sync without the need of adding
+ * a new device/user/principal in the SyncAdmin. This is for the sake of easy
+ * of use for beginners. To disable this behaviuor, just remove the synclet
+ * from the pipeline.
+ * The original device id will is replaced in the outgoing message so that the
+ * phone device will not notice the change.
+ *
+ * <p>The change is done only if the sync sources to sync are in the given
+ * <code>syncSourcesToProcess</code> list and the client device id is not in the
+ * <code>devicesNotToProcess</code> list.
+ *
+ * This synclet uses the following context properties:
+ * <ul>
+ *  <li>funambol.foundation.changedevice.DEVICEID_ORIG - to store the original device id</li>
+ * </ul>
+ *
+ * Note that because the Alert commands containing the syncsource to sync are
+ * given only in the first message, we have to remember if we are changing the
+ * deviceid or not for all the messages in the session. For this, we just check
+ * if clientDeviceId is already set. If not, we check for the alert; if yes, it
+ * means that we have already determined that the device id needs to be replaced,
+ * therefore we replace it without further checks.
+ *
+ * <p>If the DEVICEID_ORIG in the processingContext is null, no change is performed.
+ *
+ * @version $Id: ChangeDeviceIdSynclet.java,v 1.3 2007-01-11 19:20:19 nichele Exp $
+ */
+public class ChangeDeviceIdSynclet
+implements InputMessageProcessor, OutputMessageProcessor {
+
+    // --------------------------------------------------------------- Constants
+    private static final String PROPERTY_ORIG_ID =
+        "funambol.foundation.changedevice.DEVICEID_ORIG";
+
+    private static final String CLIENT_DEVICEID = "syncml-phone";
+
+    private static final String SYNCLET_NAME = ChangeDeviceIdSynclet.class.getName();
+
+    // ------------------------------------------------------------ Private data
+    private static final FunambolLogger log =
+            FunambolLoggerFactory.getLogger("engine.pipeline");
+
+    /**
+     * Contain the original client device Id
+     */
+    private String clientDeviceId = null;
+
+    /**
+     * Contains the uri of the sync sources to process.
+     * If in the message to process, there are a syncsource specified in the list,
+     * then the message is processed
+     */
+    private ArrayList syncSourcesToProcess = null;
+
+    /**
+     * Contains the list of the known devices of which the ID doesn't have
+     * to be changed.
+     */
+    private ArrayList devicesNotToProcess    = null;
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * Process input message and set MessageProcessingContext property.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     * @throws Sync4jException
+     */
+    public void preProcessMessage(MessageProcessingContext processingContext,
+                                  SyncML message) throws Sync4jException {
+
+        if (log.isTraceEnabled()) {
+            log.trace(SYNCLET_NAME + ".preProcessMessage(...)");
+        }
+
+        boolean processMessage = true;
+
+        if (clientDeviceId == null) {
+            processMessage = checkSourceUriToSync(message);
+        }
+
+        if (log.isTraceEnabled()) {
+            log.trace(SYNCLET_NAME + " - device id replacement " + (processMessage ? "" : "not ") + "required");
+        }
+
+        if (processMessage) {
+            if (clientDeviceId == null) {
+                clientDeviceId = message.getSyncHdr().getSource().getLocURI();
+            }
+
+            if (log.isTraceEnabled()) {
+                log.trace(SYNCLET_NAME + " - original clientDeviceId '" + clientDeviceId + "'");
+            }
+
+            if (!devicesNotToProcess.contains(clientDeviceId)) {
+                Source source = message.getSyncHdr().getSource();
+                source.setLocURI(CLIENT_DEVICEID);
+                processingContext.setRequestProperty(PROPERTY_ORIG_ID, clientDeviceId);
+            }
+        }
+    }
+
+
+    /**
+     * Process and manipulate the output message.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     * @throws Sync4jException
+     */
+    public void postProcessMessage(MessageProcessingContext processingContext,
+                                   SyncML message) throws Sync4jException {
+        if (log.isTraceEnabled()) {
+            log.trace(SYNCLET_NAME + ".postProcessMessage(...)");
+        }
+
+        if (clientDeviceId == null) {
+            clientDeviceId = (String)processingContext.getRequestProperty(PROPERTY_ORIG_ID);
+        }
+
+        //
+        // If still null, no processing is required
+        //
+         if (clientDeviceId == null) {
+            if (log.isTraceEnabled()) {
+                log.trace(SYNCLET_NAME + " - processing not required");
+            }
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace(SYNCLET_NAME + " - setting device id to '" + clientDeviceId + "'");
+            }
+
+            Target target = message.getSyncHdr().getTarget();
+            target.setLocURI(clientDeviceId);
+
+            //
+            // We have also to change the SourceRef in the SyncHdr's status
+            //
+            SyncBody body = message.getSyncBody();
+            AbstractCommand[] allServerCommands =
+                 (AbstractCommand[])body.getCommands().toArray(new AbstractCommand[0]);
+
+            ArrayList listStatus =
+                ProtocolUtil.filterCommands(allServerCommands,
+                                            Status.class);
+
+            if (listStatus != null && listStatus.size() != 0) {
+
+                Status status = (Status) listStatus.get(0);
+                List sourceRefList = status.getSourceRef();
+                if (sourceRefList != null && sourceRefList.size() != 0) {
+                    SourceRef sourceRef =
+                            (SourceRef)sourceRefList.get(0);
+                    sourceRef.setValue(clientDeviceId);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Set the syncSourcesToProcess
+     * @param list the syncSourcesToProcess
+     */
+    public void setSyncSourcesToProcess(ArrayList list) {
+        this.syncSourcesToProcess = list;
+    }
+
+    /**
+     * Set the devicesNotToProcess
+     *
+     * @param list the devicesNotToProcess
+     */
+    public void setDevicesNotToProcess(ArrayList list) {
+        this.devicesNotToProcess = list;
+    }
+
+    // --------------------------------------------------------- Private Methods
+
+    /**
+     * Checks if the sync sources that are in the syncml message are present
+     * in the list <code>syncSourcesToProcess</code>
+     *
+     * @param message the message to process
+     * @return boolean
+     */
+    private boolean checkSourceUriToSync(SyncML message) {
+
+        if (syncSourcesToProcess == null) {
+            return false;
+        }
+
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allClientCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList alertList = ProtocolUtil.filterCommands(allClientCommands,
+            Alert.class);
+
+        Iterator itAlertList = alertList.iterator();
+
+        Alert alert = null;
+        ArrayList items = null;
+        Iterator itItem = null;
+        Item item = null;
+        Target target = null;
+        String sourceUri = null;
+
+        while (itAlertList.hasNext()) {
+            alert = (Alert)itAlertList.next();
+            items = alert.getItems();
+            itItem = items.iterator();
+            while (itItem.hasNext()) {
+
+                item = (Item)itItem.next();
+                target = item.getTarget();
+                if (target != null) {
+                    sourceUri = target.getLocURI();
+
+                    if (syncSourcesToProcess.indexOf(sourceUri) != -1) {
+                        // the source uri is present in the list of the
+                        // sync source to process
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/StopProcessingSynclet.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/StopProcessingSynclet.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/StopProcessingSynclet.java	(revision 2082)
@@ -0,0 +1,65 @@
+/**
+ * Copyright (C) 2003-2007 Funambol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Honest Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Honest Public License for more details.
+ *
+ * You should have received a copy of the Honest Public License
+ * along with this program; if not, write to Funambol,
+ * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
+ */
+package br.com.prognus.psync.synclet;
+
+import com.funambol.framework.core.SyncML;
+import com.funambol.framework.core.Sync4jException;
+import com.funambol.framework.engine.pipeline.InputMessageProcessor;
+import com.funambol.framework.engine.pipeline.OutputMessageProcessor;
+import com.funambol.framework.engine.pipeline.MessageProcessingContext;
+import com.funambol.framework.engine.pipeline.StopProcessingException;
+
+/**
+ * This synclet simply throws a StopProcessingException at each call. It can be
+ * useful for debugging purposes.
+ *
+ * @version $Id: StopProcessingSynclet.java,v 1.2 2007-01-11 19:20:19 nichele Exp $
+ */
+public class StopProcessingSynclet
+implements InputMessageProcessor, OutputMessageProcessor {
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * Throws a StopProcessingException.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     *
+     * @throws Sync4jException
+     */
+    public void preProcessMessage(MessageProcessingContext processingContext,
+                                  SyncML                   message)
+    throws Sync4jException {
+        throw new StopProcessingException("Please stop input procesing!");
+    }
+
+    /**
+     * Logs the output message and context
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     *
+     * @throws Sync4jException
+     */
+    public void postProcessMessage(MessageProcessingContext processingContext,
+                                   SyncML                   message)
+    throws Sync4jException {
+        throw new StopProcessingException("Please stop output procesing!");
+    }
+
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/PIMItemsHandler.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/PIMItemsHandler.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/PIMItemsHandler.java	(revision 2082)
@@ -0,0 +1,387 @@
+/**
+ * Copyright (C) 2007 Funambol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Honest Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Honest Public License for more details.
+ *
+ * You should have received a copy of the Honest Public License
+ * along with this program; if not, write to Funambol,
+ * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
+ */
+package br.com.prognus.psync.synclet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.funambol.framework.core.AbstractCommand;
+import com.funambol.framework.core.Add;
+import com.funambol.framework.core.Item;
+import com.funambol.framework.core.ItemizedCommand;
+import com.funambol.framework.core.Meta;
+import com.funambol.framework.core.Replace;
+import com.funambol.framework.core.Sync;
+import com.funambol.framework.core.Sync4jException;
+import com.funambol.framework.core.SyncML;
+import com.funambol.framework.engine.pipeline.MessageProcessingContext;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+
+/**
+ * This class extracts the PIM Items from the SyncML message.
+ * It creates a Map with three lists that contain the items separated based on
+ * their type. In case of large object, this class is able to recognize the
+ * large object item and is able to handle it using a Map cached in the
+ * MessageProcessingContext.
+ *
+ * @version $Id: PIMItemsHandler.java,v 1.1 2007-02-02 10:33:13 luigiafassina Exp $
+ */
+public class PIMItemsHandler {
+
+    // --------------------------------------------------------------- Constants
+    private final FunambolLogger logger =
+        FunambolLoggerFactory.getLogger("engine.pipeline");
+
+    private static final String BEGIN_VCARD     = "BEGIN:VCARD"                ;
+    private static final String END_VCARD_RN    = "END:VCARD\r\n"              ;
+    private static final String END_VCARD_N     = "END:VCARD\n"                ;
+    private static final String BEGIN_VCALENDAR = "BEGIN:VCALENDAR"            ;
+    private static final String END_VEVENT_RN   =
+        "END:VEVENT\r\nEND:VCALENDAR\r\n";
+    private static final String END_VEVENT_N    = "END:VEVENT\nEND:VCALENDAR\n";
+    private static final String END_VTODO_RN    =
+        "END:VTODO\r\nEND:VCALENDAR\r\n" ;
+    private static final String END_VTODO_N     = "END:VTODO\nEND:VCALENDAR\n" ;
+
+    public static final String KEY_VCARD  = "VCARD" ;
+    public static final String KEY_VEVENT = "VEVENT";
+    public static final String KEY_VTODO  = "VTODO" ;
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * Processes input message to extract the items in order to separate them
+     * into lists based on their type (vcard, vevent, vtodo).
+     *
+     * @param pContext the message processing context
+     * @param message the message to be processed
+     *
+     * @return mapItems the map with the three list of items
+     * @throws Sync4jException in case of errors
+     */
+    public Map extractIncomingPIMItems(MessageProcessingContext pContext,
+                                       SyncML message)
+    throws Sync4jException {
+
+        if (logger.isTraceEnabled()) {
+            logger.trace("Starting creation of incoming items lists...");
+        }
+
+        //
+        // Caches the map with lists of items into request context: in this way,
+        // if more synclet act on the same items, is not need to recycle on
+        // items of the message
+        //
+
+        //
+        // Contains the lists of the items
+        //
+        Map mapItems = null;
+        mapItems = (Map)pContext.getRequestProperty("MAP_INCOMING_ITEMS");
+
+        if (mapItems != null) {
+            if (logger.isTraceEnabled()) {
+                logger.trace("Exists a map of items: is not need to cycle on message");
+            }
+
+            //
+            // In this case the first synclet has just created the map and so
+            // the next synclets must act on that without recycle in message.
+            //
+            return mapItems;
+        } else {
+
+            if (logger.isTraceEnabled()) {
+                logger.trace("Creates a map to contains the lists of items");
+            }
+            mapItems = new HashMap();
+            initializeMapItems(mapItems);
+        }
+
+        //
+        // Used to handle large object item
+        //
+        Item firstItem = null;
+        Item lastItem  = null;
+        //
+        // Cache the large object
+        //
+        Map cache      = null;
+        cache = (Map)pContext.getSessionProperty("LARGE_OBJ_INCOMING");
+
+        String syncURI    = null;
+        String itemLocURI = null;
+
+        List<AbstractCommand> cmds = message.getSyncBody().getCommands();
+        for (AbstractCommand bodyc : cmds) {
+
+            if (bodyc instanceof Sync) {
+                syncURI = ((Sync)bodyc).getTarget().getLocURI();
+
+                //
+                // Processes incoming commands to identifier and separate the items.
+                //
+                List<ItemizedCommand> syncCmds = ((Sync)bodyc).getCommands();
+                for (ItemizedCommand c : syncCmds) {
+
+                    //
+                    // Skip other commands than Add and Replace
+                    //
+                    if (!(Replace.COMMAND_NAME.equals(c.getName()) ||
+                        Add.COMMAND_NAME.equals(c.getName()))      ) {
+                        continue;
+                    }
+
+                    List<Item> items = c.getItems();
+                    for (Item item: items) {
+
+                        itemLocURI = ((item.getSource() != null)
+                                      ? item.getSource().getLocURI()
+                                      : item.getTarget().getLocURI());
+
+                        if (firstItem == null) {
+                            firstItem = item;
+                        }
+
+                        if (firstItem == item) {
+                            if (cache == null || cache.isEmpty()) {
+                                if (item.isMoreData()) {
+                                    if (logger.isTraceEnabled()) {
+                                        logger.trace("The item " + itemLocURI +
+                                                     " is a large object");
+                                    }
+                                    cache = handleCache(cache, item, syncURI);
+                                }
+                            } else {
+                                cache = handleCache(cache, item, syncURI);
+                            }
+                        } //end if firstItem == item
+
+                        lastItem = item;
+
+                        if (!item.isMoreData()) {
+                            mapItems = fillMapItems(mapItems, item);
+                        }
+
+                    } //end for items
+                } //end for cmds
+            } //end if Sync
+
+            if (lastItem != firstItem) {
+                if (lastItem.isMoreData()) {
+                    cache = handleCache(cache, lastItem, syncURI);
+                }
+            }
+        } //end if cmds
+
+        pContext.setSessionProperty("LARGE_OBJ_INCOMING", cache);
+        pContext.setRequestProperty("MAP_INCOMING_ITEMS", mapItems);
+
+        return mapItems;
+    }
+
+    /**
+     * Processes output message to extract the items in order to separate them
+     * into lists based on their type (vcard, vevent, vtodo).
+     *
+     * @param message the message to be processed
+     *
+     * @return mapItems the map with the three list of items
+     * @throws Sync4jException in case of errors
+     */
+    public Map extractOutgoingPIMItems(SyncML message)
+    throws Sync4jException {
+        if (logger.isTraceEnabled()) {
+            logger.trace("Starting creation of outgoing items lists...");
+        }
+
+        Map mapItems = new HashMap();
+        initializeMapItems(mapItems);
+
+        List<AbstractCommand> cmds = message.getSyncBody().getCommands();
+        for (AbstractCommand bodyc : cmds) {
+
+            if (bodyc instanceof Sync) {
+
+                //
+                // Processes incoming commands to identifier and separate the items.
+                // Note: the large object items will not be considered.
+                //
+                List<ItemizedCommand> syncCmds = ((Sync)bodyc).getCommands();
+                for (ItemizedCommand c : syncCmds) {
+
+                    //
+                    // Skip other commands than Add and Replace
+                    //
+                    if (!(Replace.COMMAND_NAME.equals(c.getName()) ||
+                        Add.COMMAND_NAME.equals(c.getName()))      ) {
+                        continue;
+                    }
+
+                    List<Item> items = c.getItems();
+                    for (Item item: items) {
+                        mapItems = fillMapItems(mapItems, item);
+                    }//end for i items
+                }//end for c commands
+            }//end if Sync
+        }
+
+        return mapItems;
+    }
+
+    /**
+     * If the item has the size set, probably it is a large object item. In this
+     * case, after the synclet modifications, is need to fix the size property
+     * with the real dimension of the data's item.
+     *
+     * @param i the item to fix the size property
+     */
+    public void fixLargeObjectSize(Item i) {
+
+        Meta m = i.getMeta();
+        if (m == null) {
+            return;
+        }
+        if (m.getSize() != null && m.getSize() != 0) {
+            int size = i.getData().getData().length();
+            if (logger.isTraceEnabled()) {
+                logger.trace("Fixed size of large object item from " +
+                             m.getSize() + " to " + size);
+            }
+            i.getMeta().setSize(new Long(size));
+        }
+    }
+
+    // --------------------------------------------------------- Private methods
+
+    /**
+     * Handles large object storing its information into a Map.
+     *
+     * @param cache The map in which store the large object informations
+     * @param item the large object
+     * @param source the source with LocURI of item
+     *
+     * @return the map in which the large object is cached
+     */
+    private Map handleCache(Map cache, Item item, String source) {
+
+        if (cache == null || cache.isEmpty()) {
+            //
+            // It is the first chunk of the large object item
+            //
+            String uri = (item.getSource() != null)
+                       ? item.getSource().getLocURI()
+                       : item.getTarget().getLocURI()
+                       ;
+            cache = new HashMap();
+            cache.put("itemLocURI", uri);
+            cache.put("syncLocURI", source);
+            cache.put("data", item.getData().getData());
+
+        } else {
+
+            String cacheData   = (String)cache.get("data");
+            String cacheLocURI = (String)cache.get("syncLocURI")
+                               + "/"
+                               + (String)cache.get("itemLocURI")
+                               ;
+
+            String itemLocURI = source + "/" + ((item.getSource() != null)
+                                                ? item.getSource().getLocURI()
+                                                : item.getTarget().getLocURI());
+
+            if (cacheLocURI.equals(itemLocURI)) {
+                cacheData = cacheData + item.getData().getData();
+
+                if (item.isMoreData()) {
+                    cache.put("data", cacheData);
+                } else {
+                    item.getData().setData(cacheData);
+                    if (item.getMeta() == null) {
+                        item.setMeta(new Meta());
+                    }
+                    item.getMeta().setSize(new Long(cacheData.length()));
+                    cache.clear();
+                }
+            } else {
+                cache.clear();
+            }
+        }
+
+        return cache;
+    }
+
+    /**
+     * Fills the maps of items separated based on their type (vcard, vevent,
+     * vtodo).
+     *
+     * @param mapItems the map in which add the list of items separated based on
+     *                 them type
+     * @param item the item to handle
+     *
+     * @return the map with lists of items
+     */
+    private Map fillMapItems(Map mapItems, Item item) {
+
+        List vcardItems  = (List)mapItems.get(KEY_VCARD );
+        List veventItems = (List)mapItems.get(KEY_VEVENT);
+        List vtodoItems  = (List)mapItems.get(KEY_VTODO );
+
+        String data = item.getData().getData();
+
+        if ( data.startsWith(BEGIN_VCARD) &&
+            (data.endsWith(END_VCARD_RN) ||
+            data.endsWith(END_VCARD_N)    )) {
+
+            vcardItems.add(item);
+        } else if (data.startsWith(BEGIN_VCALENDAR)) {
+            if (data.endsWith(END_VEVENT_RN) ||
+                data.endsWith(END_VEVENT_N)    ) {
+
+                veventItems.add(item);
+            } else if (data.endsWith(END_VTODO_RN) ||
+                data.endsWith(END_VTODO_N)    ) {
+
+                vtodoItems.add(item);
+            }
+        }
+        if (logger.isTraceEnabled()) {
+            logger.trace("List vcard : " + vcardItems.toString() );
+            logger.trace("List vevent: " + veventItems.toString());
+            logger.trace("List vtodo : " + vtodoItems.toString() );
+        }
+        return mapItems;
+    }
+
+    /**
+     * Initializes the Map of items.
+     *
+     * @param mapItems the empty map
+     */
+    private void initializeMapItems(Map mapItems) {
+
+        List vcardItems  = new ArrayList();
+        List veventItems = new ArrayList();
+        List vtodoItems  = new ArrayList();
+
+        mapItems.put(KEY_VCARD , vcardItems );
+        mapItems.put(KEY_VEVENT, veventItems);
+        mapItems.put(KEY_VTODO , vtodoItems );
+    }
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/ChangeSourceUriSynclet.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/ChangeSourceUriSynclet.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/ChangeSourceUriSynclet.java	(revision 2082)
@@ -0,0 +1,528 @@
+/**
+ * Copyright (C) 2003-2007 Funambol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Honest Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Honest Public License for more details.
+ *
+ * You should have received a copy of the Honest Public License
+ * along with this program; if not, write to Funambol,
+ * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
+ */
+package br.com.prognus.psync.synclet;
+
+import java.util.*;
+
+import com.funambol.framework.core.*;
+import com.funambol.framework.core.Map;
+import com.funambol.framework.engine.pipeline.*;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+import com.funambol.framework.protocol.ProtocolUtil;
+
+/**
+ * This class changes the input message client source URI with a correspondent
+ * server source URI
+ *
+ * This is done in order to allow any client to sync with different source URI
+ * using the same SyncSource. For example into phone is possible to write
+ * ./contact or only contact to synchorized with the same contact SyncSource.
+ *
+ * @version $Id: ChangeSourceUriSynclet.java,v 1.5 2007-02-15 10:23:46 luigiafassina Exp $
+ */
+public class ChangeSourceUriSynclet
+implements InputMessageProcessor, OutputMessageProcessor {
+
+    // --------------------------------------------------------------- Constants
+    private static final String SYNCLET_NAME =
+        ChangeSourceUriSynclet.class.getName();
+    private static final String PROPERTY_SOURCENAME_CHANGED =
+        "funambol.foundation.changesourcename.SOURCENAME_CHANGED";
+
+    // ------------------------------------------------------------ Private data
+    private static final FunambolLogger log =
+            FunambolLoggerFactory.getLogger("engine.pipeline");
+
+    private HashMap sourceNameMapping = null;
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * Process input message and set MessageProcessingContext property.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     * @throws Sync4jException
+     */
+    public void preProcessMessage(MessageProcessingContext processingContext,
+                                  SyncML message                            )
+    throws Sync4jException {
+
+        if (log.isTraceEnabled()) {
+            log.trace(SYNCLET_NAME + ".preProcessMessage(...)");
+        }
+
+        HashMap sourceReplacedMap =
+            (HashMap)processingContext.getSessionProperty(
+                                                   PROPERTY_SOURCENAME_CHANGED);
+
+        if (sourceReplacedMap == null) {
+            sourceReplacedMap = new HashMap();
+            processingContext.setSessionProperty(PROPERTY_SOURCENAME_CHANGED,
+                                                 sourceReplacedMap          );
+        }
+
+        //
+        // Store and change the source uri into commands
+        //
+        manageInputAlert (message, sourceReplacedMap);
+        manageInputStatus(message, sourceReplacedMap);
+        manageInputSync  (message, sourceReplacedMap);
+        manageInputMap   (message, sourceReplacedMap);
+
+    }
+
+    /**
+     * Process and manipulate the output message.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     * @throws Sync4jException
+     */
+    public void postProcessMessage(MessageProcessingContext processingContext,
+                                   SyncML message) throws Sync4jException {
+        if (log.isTraceEnabled()) {
+            log.trace(SYNCLET_NAME + ".postProcessMessage(...)");
+        }
+
+        HashMap sourceReplacedMap =
+            (HashMap)processingContext.getSessionProperty(
+                                                   PROPERTY_SOURCENAME_CHANGED);
+        if (!sourceReplacedMap.isEmpty()) {
+            //
+            // Replace original source uri into commands
+            //
+            manageOutputStatus (message, sourceReplacedMap);
+            manageOutputResults(message, sourceReplacedMap);
+            manageOutputAlert  (message, sourceReplacedMap);
+            manageOutputSync   (message, sourceReplacedMap);
+       }
+    }
+
+    /**
+     * Set the hashmap with sources name's to change
+     *
+     * @param map
+     */
+    public void setSourceNameMapping(HashMap map) {
+        this.sourceNameMapping = map;
+    }
+
+    /**
+     * Get the hashmap with sources name's to change
+     *
+     * @return the map
+     */
+    public HashMap getSourceNameMapping() {
+        return this.sourceNameMapping;
+    }
+    // --------------------------------------------------------- Private Methods
+
+    /**
+     * Store and change Target URI into Alert commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap in which store uri to replace
+     */
+    private void manageInputAlert(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Change TargetURI into input Alert commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allClientCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList alertList =
+            ProtocolUtil.filterCommands(allClientCommands, Alert.class);
+
+        Iterator itAlertList = alertList.iterator();
+
+        Alert alert = null;
+        ArrayList items = null;
+        Iterator itItem = null;
+        Item item = null;
+        Target target = null;
+        String targetUri = null;
+
+        while (itAlertList.hasNext()) {
+            alert = (Alert)itAlertList.next();
+            items = alert.getItems();
+            itItem = items.iterator();
+            while (itItem.hasNext()) {
+
+                item = (Item)itItem.next();
+                target = item.getTarget();
+                if (target != null) {
+                    targetUri = target.getLocURI();
+                    if (log.isTraceEnabled()) {
+                        log.trace("original targetUri: " + targetUri);
+                    }
+
+                    if (sourceNameMapping.containsKey(targetUri)) {
+                        String targetNew =
+                            (String)sourceNameMapping.get(targetUri);
+
+                        if (log.isTraceEnabled()) {
+                            log.trace("new targetUri: " + targetNew);
+                        }
+                        target.setLocURI(targetNew);
+
+                        sourceReplacedMap.put(targetNew, targetUri);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Store and change SourceRef into Status commmads
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap in which store uri to replace
+     */
+    private void manageInputStatus(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Change SourceRef into input Status commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allClientCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList statusList =
+            ProtocolUtil.filterCommands(allClientCommands, Status.class);
+
+        Iterator itStatusList = statusList.iterator();
+
+        Status status = null;
+        while (itStatusList.hasNext()) {
+            status = (Status)itStatusList.next();
+            SourceRef[] srefs =
+                (SourceRef[])status.getSourceRef().toArray(new SourceRef[0]);
+            int s = srefs.length;
+            for(int i=0;i<s;i++) {
+                SourceRef sr = (SourceRef)srefs[i];
+                String sourceRef = sr.getValue();
+                if (log.isTraceEnabled()) {
+                    log.trace("original sourceRef: " + sourceRef);
+                }
+
+                if (sourceNameMapping.containsKey(sourceRef)) {
+                    String sourceRefNew =
+                        (String)sourceNameMapping.get(sr.getValue());
+
+                    if (log.isTraceEnabled()) {
+                        log.trace("new sourceRef: " + sourceRefNew);
+                    }
+                    sr.setValue(sourceRefNew);
+
+                    sourceReplacedMap.put(sourceRefNew, sourceRef);
+                }
+            }
+        }
+    }
+
+    /**
+     * Store and change Target into Sync commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap in which store uri to replace
+     */
+    private void manageInputSync(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Change TargetURI into input Sync commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allClientCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList syncList =
+            ProtocolUtil.filterCommands(allClientCommands, Sync.class);
+
+        Iterator itList = syncList.iterator();
+
+        Sync sync = null;
+        String targetUri = null;
+
+        while (itList.hasNext()) {
+            sync = (Sync)itList.next();
+            targetUri = sync.getTarget().getLocURI();
+            if (log.isTraceEnabled()) {
+                log.trace("original targetUri: " + targetUri);
+            }
+
+            if (sourceNameMapping.containsKey(targetUri)) {
+                String targetNew = (String)sourceNameMapping.get(targetUri);
+
+                if (log.isTraceEnabled()) {
+                    log.trace("new targetUri: " + targetNew);
+                }
+                sync.getTarget().setLocURI(targetNew);
+
+                sourceReplacedMap.put(targetNew, targetUri);
+            }
+        }
+    }
+
+    /**
+     * Store and change Target into Map commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap in which store uri to replace
+     */
+    private void manageInputMap(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Change TargetURI into input Map commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allClientCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList mapList =
+            ProtocolUtil.filterCommands(allClientCommands, Map.class);
+
+        Iterator itList = mapList.iterator();
+
+        Map map = null;
+        String targetUri = null;
+
+        while (itList.hasNext()) {
+            map = (Map)itList.next();
+            targetUri = map.getTarget().getLocURI();
+            if (log.isTraceEnabled()) {
+                log.trace("original targetUri: " + targetUri);
+            }
+
+            if (sourceNameMapping.containsKey(targetUri)) {
+                String targetNew = (String)sourceNameMapping.get(targetUri);
+
+                if (log.isTraceEnabled()) {
+                    log.trace("new targetUri: " + targetNew);
+                }
+                map.getTarget().setLocURI(targetNew);
+
+                sourceReplacedMap.put(targetNew, targetUri);
+            }
+        }
+    }
+
+    /**
+     * Replace TargetRef into Status commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap with the uri to replace
+     */
+    private void manageOutputStatus(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Replace TargetRef into output Status commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allServerCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList statusList =
+            ProtocolUtil.filterCommands(allServerCommands, Status.class);
+
+        Iterator itStatusList = statusList.iterator();
+
+        Status status = null;
+        while (itStatusList.hasNext()) {
+            status = (Status)itStatusList.next();
+
+            if (status.getTargetRef() == null) {
+                continue;
+            }
+            TargetRef[] trefs =
+                (TargetRef[])status.getTargetRef().toArray(new TargetRef[0]);
+            int s = trefs.length;
+            for(int i=0;i<s;i++) {
+                TargetRef tr = (TargetRef)trefs[i];
+                if (sourceReplacedMap.containsKey(tr.getValue())) {
+                    tr.setValue((String)sourceReplacedMap.get(tr.getValue()));
+                }
+            }
+        }
+    }
+
+    /**
+     * Replace Source into Alert commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap with the uri to replace
+     */
+    private void manageOutputAlert(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Replace Source into output Alert commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allServerCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList alertList =
+            ProtocolUtil.filterCommands(allServerCommands, Alert.class);
+
+        Iterator itAlertList = alertList.iterator();
+
+        Alert alert = null;
+        ArrayList items = null;
+        Iterator itItem = null;
+        Item item = null;
+        Source source = null;
+        String sourceUri = null;
+
+        while (itAlertList.hasNext()) {
+            alert = (Alert)itAlertList.next();
+            items = alert.getItems();
+            itItem = items.iterator();
+            while (itItem.hasNext()) {
+
+                item = (Item)itItem.next();
+                source = item.getSource();
+                if (source != null) {
+                    sourceUri = source.getLocURI();
+
+                    if (sourceReplacedMap.containsKey(sourceUri)) {
+                        //
+                        // Here we have to create a new Source object because
+                        // the Alert can contain the same Source used in the
+                        // engine/sessionHandler to handle the Database.
+                        // If here we change the source object of the alert we
+                        // change also the source of the database because it's
+                        // the same object!!.
+                        //
+                        //
+                        Source newSource = new Source(
+                            (String)sourceReplacedMap.get(sourceUri),
+                            source.getLocName()
+                        );
+                        item.setSource(newSource);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Replace SourceRef into Results command
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap with the uri to replace
+     */
+    private void manageOutputResults(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Replace SourceRef into output Result commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allServerCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        List list =
+            ProtocolUtil.filterCommands(allServerCommands, Results.class);
+
+        if (list.isEmpty()) {
+            return;
+        }
+
+        Results results = (Results)list.get(0);
+
+        Item[] items = (Item[])results.getItems().toArray(new Item[0]);
+
+        if (items != null && items.length > 0) {
+            if (items[0] instanceof DevInfItem) {
+                DevInfItem item = (DevInfItem) items[0];
+                ArrayList dss = item.getDevInfData().getDevInf().getDataStores();
+                int s = dss.size();
+                for (int i = 0; i < s; i++) {
+                    DataStore ds = (DataStore) dss.get(i);
+                    String sourceRef = ds.getSourceRef().getValue();
+                    if (sourceReplacedMap.containsKey(sourceRef)) {
+                        ds.getSourceRef().setValue((String) sourceReplacedMap.
+                                get(sourceRef));
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Replace Source into Sync commands
+     *
+     * @param message the client message
+     * @param sourceReplacedMap the HashMap with the uri to replace
+     */
+    private void manageOutputSync(SyncML message, HashMap sourceReplacedMap) {
+        if (log.isTraceEnabled()) {
+            log.trace("Replace Source into output Sync commands");
+        }
+        SyncBody syncBody = message.getSyncBody();
+
+        AbstractCommand[] allServerCommands =
+            (AbstractCommand[])syncBody.getCommands().toArray(
+            new AbstractCommand[0]);
+
+        ArrayList syncList =
+            ProtocolUtil.filterCommands(allServerCommands, Sync.class);
+
+        Iterator itSyncList = syncList.iterator();
+
+        Sync sync        = null;
+        Source source    = null;
+        String sourceUri = null;
+
+        while (itSyncList.hasNext()) {
+            sync = (Sync) itSyncList.next();
+
+            source = sync.getSource();
+
+            if (source != null) {
+                sourceUri = source.getLocURI();
+
+                if (sourceReplacedMap.containsKey(sourceUri)) {
+                    //
+                    // Here we have to create a new Source object because
+                    // the Sync can contain the same Source used in the
+                    // engine/sessionHandler to handle the Database.
+                    // If here we change the source object of the sync commands we
+                    // change also the source of the database because it's
+                    // the same object!!.
+                    //
+                    //
+                    Source newSource = new Source(
+                            (String) sourceReplacedMap.get(sourceUri),
+                            source.getLocName()
+                                       );
+                    sync.setSource(newSource);
+                }
+            }
+        }
+    }
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/LoggingSynclet.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/LoggingSynclet.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/LoggingSynclet.java	(revision 2082)
@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 2003-2007 Funambol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Honest Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Honest Public License for more details.
+ *
+ * You should have received a copy of the Honest Public License
+ * along with this program; if not, write to Funambol,
+ * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
+ */
+package br.com.prognus.psync.synclet;
+
+import com.funambol.framework.core.Sync4jException;
+import com.funambol.framework.core.SyncML;
+import com.funambol.framework.engine.pipeline.*;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+import com.funambol.framework.tools.SyncMLUtil;
+
+/**
+ * This class just logs input and output messages. This synclet can then be
+ * inserted in any position in a pipeline in order to trace how a message is
+ * changed during the pipeline processing. It is at the same time an input and
+ * an output pipeline component.<br>
+ * Logging is done at INFO level
+ *
+ * @version $Id: LoggingSynclet.java,v 1.4 2007-01-11 19:20:19 nichele Exp $
+ */
+public class LoggingSynclet implements InputMessageProcessor, OutputMessageProcessor {
+
+    // ------------------------------------------------------------ Private data
+
+    private static final FunambolLogger log =
+            FunambolLoggerFactory.getLogger("engine.pipeline");
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * Logs the input message and context
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     *
+     * @throws Sync4jException
+     */
+    public void preProcessMessage(MessageProcessingContext processingContext,
+                                  SyncML                   message)
+    throws Sync4jException {
+        if (log.isInfoEnabled()) {
+            log.info("--------------------------------------------------------------------------------");
+            log.info("Input message processing context"                                                );
+            log.info("                                                                                ");
+            log.info(processingContext.toString()                                                      );
+            log.info("--------------------------------------------------------------------------------");
+            log.info("Input message"                                                                   );
+            log.info("                                                                                ");
+            log.info(SyncMLUtil.toXML(message)                                                               );
+            log.info("--------------------------------------------------------------------------------");
+        }
+    }
+
+    /**
+     * Logs the output message and context
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     *
+     * @throws Sync4jException
+     */
+    public void postProcessMessage(MessageProcessingContext processingContext,
+                                   SyncML                   message)
+    throws Sync4jException {
+        if (log.isInfoEnabled()) {
+            log.info("--------------------------------------------------------------------------------");
+            log.info("Output message processing context"                                               );
+            log.info("                                                                                ");
+            log.info(processingContext.toString()                                                      );
+            log.info("--------------------------------------------------------------------------------");
+            log.info("Output message"                                                                  );
+            log.info("                                                                                ");
+            log.info(SyncMLUtil.toXML(message)                                                               );
+            log.info("--------------------------------------------------------------------------------");
+        }
+    }
+
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MySyncSource.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MySyncSource.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MySyncSource.java	(revision 2082)
@@ -0,0 +1,414 @@
+/*
+ * Funambol is a mobile platform developed by Funambol, Inc.
+ * Copyright (C) 2008 Funambol, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Affero General Public License version 3 as published by
+ * the Free Software Foundation with the addition of the following permission
+ * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
+ * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
+ * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses or write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA.
+ *
+ * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
+ * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU Affero General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU Affero General Public License
+ * version 3, these Appropriate Legal Notices must retain the display of the
+ * "Powered by Funambol" logo. If the display of the logo is not reasonably
+ * feasible for technical reasons, the Appropriate Legal Notices must display
+ * the words "Powered by Funambol".
+ */
+
+package br.com.prognus.psync;
+
+
+import java.sql.Timestamp;
+import java.util.HashMap;
+
+import com.funambol.framework.engine.*;
+import com.funambol.framework.engine.source.*;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+import com.funambol.framework.tools.beans.LazyInitBean;
+
+/**
+ * This is a simple SyncSource prototype. It implements the methods required by
+ * the SyncSource interface.
+ */
+public class MySyncSource extends AbstractSyncSource
+    implements SyncSource, LazyInitBean {
+
+    // --------------------------------------------------------------- Constants
+
+    public static final String LOG_NAME = "myconnector";
+    
+    protected static final FunambolLogger log = FunambolLoggerFactory.getLogger(LOG_NAME);
+
+
+    // ------------------------------------------------------------ Private data
+
+    
+    // -------------------------------------------------------------- Properties
+
+    /**
+     * A string parameter
+     */
+    private String myString = null;
+
+    /**
+     * Getter for property myString
+     * @return myString
+     */
+    public String getMyString() {
+        return myString;
+    }
+
+    /**
+     * Setter for property myString
+     * @param myString new value
+     */
+    public void setMyString(String myString) {
+        this.myString = myString;
+    }
+
+    /**
+     * An int parameter
+     */
+    private int myInt;
+
+    /**
+     * Getter for property myInt
+     * @return myInt
+     */
+    public int getMyInt() {
+        return myInt;
+    }
+    
+    /**
+     * Setter for property myInt
+     * @param myInt new value
+     */
+    public void setMyInt(int myInt) {
+        this.myInt = myInt;
+    }
+    
+    /**
+     * An complex parameter
+     */
+    private HashMap myMap;
+
+    /**
+     * Getter for property myMap
+     * @return myMap
+     */
+    public HashMap getMyMap() {
+        return myMap;
+    }
+    
+    /**
+     * Setter for property myMap
+     * @param myMap new value
+     */
+    public void setMyMap(HashMap myMap) {
+        this.myMap = myMap;
+    }
+
+    // ------------------------------------------------------------ Constructors
+
+    public MySyncSource() {
+    }
+
+    // ---------------------------------------------------------- Public Methods
+
+    /**
+     * Invoked after class instantiation when a server bean is loaded.
+     */
+    public void init() {
+        //
+        // When a bean is created, first of all the empty constructor is called
+        // and the the bean properties are set. If a bean requires that its 
+        // properties have a proper value at initialization time, it must 
+        // implement com.funambol.framework.tools.beans.LazyInitBean, so that 
+        // the server beans factory has the opportunity to initialize the bean 
+        // after its creation, but before using it.
+        //
+        log.info("Initializing " + getClass().getName());
+        log.info("myString: " + myString);
+        log.info("myInt: "    + myInt   );
+        log.info("myMap: "    + myMap   );
+    }
+    
+    /**
+     * Called before any other synchronization method. To interrupt the sync
+     * process, throw a SyncSourceException.
+     *
+     * @param syncContext the context of the sync.
+     *
+     * @see SyncContext
+     *
+     * @throws SyncSourceException to interrupt the process with an error
+     */
+    public void beginSync(SyncContext syncContext) throws SyncSourceException {
+        log.info("Starting synchronization: " + syncContext);
+        
+        //
+        // Put here your code if you need to do anything before data exchange
+        //
+    }
+    
+    /**
+     * Called after the modifications have been applied.
+     *
+     * @throws SyncSourceException to interrupt the process with an error
+     */
+    public void endSync() throws SyncSourceException {
+        log.info("Ending synchronization");
+        
+        //
+        // Put here your code if you need to do anything after data exchange
+        //
+    }
+
+    /**
+     * Commits the changes applied during the sync session. If the underlying
+     * datastore can not commit the changes, a SyncSourceException is thrown.
+     *
+     * @throws SyncSourceException if the changes cannot be committed
+     */
+    public void commitSync() throws SyncSourceException {
+        log.info("Committing synchronization");
+        
+        //
+        // Put here your code if you need to do anything to finally commit 
+        // changed data
+        //
+    }
+    
+    /**
+     * Called to get the keys of all items accordingly with the parameters
+     * used in the beginSync call.
+     * @return an array of all <code>SyncItemKey</code>s stored in this source.
+     *         If there are no items an empty array is returned.
+     *
+     * @throws SyncSourceException in case of error (for instance if the
+     *         underlying data store runs into problems)
+     */
+    public SyncItemKey[] getAllSyncItemKeys()
+    throws SyncSourceException {
+        //
+        // Put here your code to retrieve the keys for the all items in the data
+        // store
+        //
+        log.info("getAllSyncItemKeys()");
+        return new SyncItemKey[0];
+    }
+    
+   /**
+     * Called to get the keys of the items updated in the time frame sinceTs - untilTs.
+     * <br><code>sinceTs</code> null means all keys of the items updated until <code>untilTs</code>.
+     * <br><code>untilTs</code> null means all keys of the items updated since <code>sinceTs</code>.
+     *
+     * @param sinceTs consider the changes since this point in time.
+     * @param untilTs consider the changes until this point in time.
+     *
+     * @return an array of keys containing the <code>SyncItemKey</code>'s key of the updated
+     *         items in the given time frame. It MUST NOT return null for
+     *         no keys, but instad an empty array.
+     */
+    public SyncItemKey[] getUpdatedSyncItemKeys(Timestamp sinceTs ,
+                                                Timestamp untilTs )
+    throws SyncSourceException {
+        //
+        // Put here your code to retrieve the keys for the changed items
+        //
+        log.info("getUpdatedSyncItemKeys()");
+        return new SyncItemKey[0];
+    }
+
+    /**
+     * Called to get the keys of the items deleted in the time frame sinceTs - untilTs.
+     * <br><code>sinceTs</code> null means all keys of the items deleted until <code>untilTs</code>.
+     * <br><code>untilTs</code> null means all keys of the items deleted since <code>sinceTs</code>.
+     *
+     * @param sinceTs consider the changes since this point in time.
+     * @param untilTs consider the changes until this point in time.
+     *
+     * @return an array of keys containing the <code>SyncItemKey</code>'s key of the deleted
+     *         items in the given time frame. It MUST NOT return null for
+     *         no keys, but instad an empty array.
+     */
+    public SyncItemKey[] getDeletedSyncItemKeys(Timestamp sinceTs ,
+                                                Timestamp untilTs )
+    throws SyncSourceException {
+        //
+        // Put here your code to retrieve the keys for the deleted items
+        //
+        log.info("getDeletedSyncItemKeys()");
+        return new SyncItemKey[0];
+    }
+
+    /**
+     * Called to get the keys of the items created in the time frame sinceTs - untilTs.
+     * <br><code>sinceTs</code> null means all keys of the items created until <code>untilTs</code>.
+     * <br><code>untilTs</code> null means all keys of the items created since <code>sinceTs</code>.
+     *
+     * @param sinceTs consider the changes since this point in time.
+     * @param untilTs consider the changes until this point in time.
+     *
+     * @return an array of keys containing the <code>SyncItemKey</code>'s key of the created
+     *         items in the given time frame. It MUST NOT return null for
+     *         no keys, but instad an empty array.
+     */
+    public SyncItemKey[] getNewSyncItemKeys(Timestamp sinceTs ,
+                                            Timestamp untilTs )
+    throws SyncSourceException {
+        //
+        // Put here your code to retrieve the keys for the new items
+        //
+        log.info("getNewSyncItemKeys()");
+        return new SyncItemKey[0];
+    }
+
+
+    /**
+     * Adds a new <code>SyncItem</code>.
+     * The item is also returned giving the opportunity to the
+     * source to modify its content and return the updated item (i.e. updating
+     * the id to the GUID).
+     *
+     * @param syncInstance  the item to add
+     *
+     * @return the inserted item
+     *
+     * @throws SyncSourceException in case of error (for instance if the
+     *         underlying data store runs into problems)
+     */
+    public SyncItem addSyncItem(SyncItem syncInstance)
+    throws SyncSourceException {
+        //
+        // Put here your code to add a new item in the data store. Remember
+        // to return the item with the new global id (GUID). 
+        //
+        log.info("addSyncItem()");
+        return null;
+    }
+
+    /**
+     * Update a <code>SyncItem</code>.
+     * The item is also returned giving the opportunity to the
+     * source to modify its content and return the updated item (i.e. updating
+     * the id to the GUID).
+     *
+     * @param syncInstance the item to replace
+     *
+     * @return the updated item
+     *
+     * @throws SyncSourceException in case of error (for instance if the
+     *         underlying data store runs into problems)
+     */
+    public SyncItem updateSyncItem(SyncItem syncInstance)
+       throws SyncSourceException {
+        //
+        // Put here your code to update an item in the data store.
+        //
+        log.info("updateSyncItem()");
+        return null;
+    }
+
+
+    /**
+     * Removes a SyncItem given its key.
+     *
+     * @param itemKey the key of the item to remove
+     * @param time the time of the deletion
+     * @param softDelete is a soft delete ?
+     *
+     * @throws SyncSourceException in case of error (for instance if the
+     *         underlying data store runs into problems)
+     */
+    public void removeSyncItem(SyncItemKey itemKey, Timestamp time, boolean softDelete)
+    throws SyncSourceException {
+        //
+        // Put here your code to remove an item from the data store.
+        //
+        log.info("removeSyncItem()");
+    }
+
+    
+    /**
+     * Called to get the item with the given key.
+     *
+     * @return return the <code>SyncItem</code> corresponding to the given
+     *         key. If no item is found, null is returned.
+     *
+     * @param syncItemKey the key of the SyncItem to return
+     *
+     * @throws SyncSourceException in case of errors (for instance if the
+     *         underlying data store runs into problems)
+     */
+    public SyncItem getSyncItemFromId(SyncItemKey syncItemKey) 
+    throws SyncSourceException {
+        //
+        // Put here your code to return a SyncItem from its Id
+        //
+        log.info("getSyncItemFromId()");
+        return null;
+    }
+
+    
+    /**
+     * Called to retrive the keys of the twins of the given item
+     *
+     * @param syncItem the twin item
+     *
+     * @return the keys of the twin. Each source implementation is free to
+     *         interpret this as it likes (i.e.: comparing all fields).
+     *
+     *
+     * @throws SyncSourceException in case of errors (for instance if the
+     *         underlying data store runs into problems)
+     */
+    public SyncItemKey[] getSyncItemKeysFromTwin(SyncItem syncItem) 
+    throws SyncSourceException {
+        //
+        // Put here your code to identify the twins (keys) of the given item
+        //
+        log.info("getSyncItemKeysFromTwin()");
+        return new SyncItemKey[0];
+    }
+
+
+    /**
+     * Called by the engine to notify an operation status.
+     * @param operationName the name of the operation.
+     *        One between:
+     *        - Add
+     *        - Replace
+     *        - Delete
+     * @param status the status of the operation
+     * @param keys the keys of the items
+     */
+    public void setOperationStatus(String operationName, int status, SyncItemKey[] keys) {
+        //
+        // Put here your code to handle the status code returned by the client
+        // for a particular operation of the given keys
+        //
+        log.info("setOperationStatus()");
+    }
+
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMContactSyncSource.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMContactSyncSource.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMContactSyncSource.java	(revision 2082)
@@ -0,0 +1,622 @@
+/**
+ *
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.engine.source;
+
+import java.io.ByteArrayInputStream;
+import java.sql.Timestamp;
+import java.util.List;
+
+import br.com.prognus.psync.exception.EntityException;
+import br.com.prognus.psync.items.manager.PIMContactManager;
+
+import com.funambol.common.pim.contact.Contact;
+import com.funambol.common.pim.converter.ContactToSIFC;
+import com.funambol.common.pim.converter.ContactToVcard;
+import com.funambol.common.pim.sif.SIFCParser;
+import com.funambol.common.pim.vcard.VcardParser;
+import com.funambol.framework.engine.SyncItem;
+import com.funambol.framework.engine.SyncItemImpl;
+import com.funambol.framework.engine.SyncItemKey;
+import com.funambol.framework.engine.SyncItemState;
+import com.funambol.framework.engine.source.SyncContext;
+import com.funambol.framework.engine.source.SyncSourceException;
+import com.funambol.framework.tools.beans.BeanInitializationException;
+
+public class PIMContactSyncSource extends PIMSyncSource {
+
+	// --------------------------------------------------------------Private
+	// data
+
+	private transient PIMContactManager manager;
+
+	// ------------------------------------------------------------Public
+	// methods
+
+	@Override
+	public void beginSync(SyncContext context) {
+
+		this.manager = new PIMContactManager(JNDI_DATA_SOURCE_NAME, context
+				.getPrincipal());
+		super.manager = this.manager;
+		super.beginSync(context);
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing the ID(s) of the
+	 * twin(s) of a given contact.
+	 * 
+	 * @param syncItem
+	 *            the SyncItem representing the contact whose twin(s) are looked
+	 *            for
+	 * @throws SyncSourceException
+	 * @return possibly, just one or no key should be in the array, but it can't
+	 *         be ruled out a priori that several keys get returned by this
+	 *         method
+	 */
+	@Override
+	public SyncItemKey[] getSyncItemKeysFromTwin(SyncItem syncItem)
+			throws SyncSourceException {
+
+		try {
+			List idList = this.manager.getTwins(convert(syncItem));
+			SyncItemKey[] keyList = new SyncItemKey[idList.size()];
+			for (int i = 0; i < idList.size(); i++) {
+				keyList[i] = new SyncItemKey(idList.get(i));
+			}
+			return keyList;
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error retrieving twin item keys.", e);
+		}
+	}
+
+	/**
+	 * Adds a SyncItem object (representing a contact).
+	 * 
+	 * @param syncItem
+	 *            the SyncItem representing the contact
+	 * 
+	 * @return a newly created syncItem based on the input object but with its
+	 *         status set at SyncItemState.NEW and the GUID retrieved by the
+	 *         back-end
+	 */
+	@Override
+	public SyncItem addSyncItem(SyncItem syncItem) throws SyncSourceException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("PIMContactSyncSource addSyncItem begin");
+		}
+
+		Contact c = null;
+		String content = null;
+
+		try {
+
+			content = getContentFromSyncItem(syncItem);
+			String contentType = syncItem.getType();
+
+			c = convert(content, contentType);
+			Timestamp ts = syncItem.getTimestamp();
+
+			// Adds the contact, wraps it in sync information and uses it to
+			// create a new SyncItem which is the return value of this method
+			SyncItemImpl newSyncItem = new SyncItemImpl(this, // syncSource
+					this.manager.addItem(c, ts), // key
+					null, // mappedKey
+					SyncItemState.NEW, // state
+					content.getBytes(), // content
+					null, // format
+					contentType, // type
+					ts // timestamp
+			);
+
+			return newSyncItem;
+
+		} catch (Exception e) {
+			log.error("SyncSource error adding a new synchronization item", e);
+			throw new SyncSourceException("Error adding the item " + syncItem,
+					e);
+		}
+	}
+
+	/**
+	 * Updates a SyncItem object (representing a contact).
+	 * 
+	 * @param syncItem
+	 *            the SyncItem representing the contact
+	 * 
+	 * @return a newly created syncItem based on the input object but with its
+	 *         status set at SyncItemState.UPDATED and the GUID retrieved by the
+	 *         back-end
+	 */
+	@Override
+	public SyncItem updateSyncItem(SyncItem syncItem)
+			throws SyncSourceException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("updateSyncItem from " + this.sourceURI);
+		}
+
+		Contact c = null;
+		String content = null;
+
+		try {
+
+			String id = syncItem.getKey().getKeyAsString();
+			content = getContentFromSyncItem(syncItem);
+			String contentType = syncItem.getType();
+
+			c = convert(content, contentType);
+
+			// Modifies the contact, wraps it in sync information and uses it to
+			// create a new SyncItem which is the return value of this method
+			SyncItemImpl newSyncItem = new SyncItemImpl(this, // syncSource
+					this.manager.updateItem(id, c, syncItem.getTimestamp()), // key
+					null, // mappedKey
+					SyncItemState.UPDATED, // state
+					content.getBytes(), // content
+					null, // format
+					contentType, // type
+					null // timestamp
+			);
+
+			return newSyncItem;
+
+		} catch (Exception e) {
+			log
+					.error(
+							"SyncSource error updating a new synchronization item",
+							e);
+			throw new SyncSourceException(
+					"Error updating the item " + syncItem, e);
+		}
+	}
+
+	/**
+	 * Deletes the item with a given syncItemKey.
+	 * 
+	 * @param syncItemKey
+	 * @param timestamp
+	 *            in case of a soft deletion, this will be the registered moment
+	 *            of deletion; if a hard deletion is used, this field is
+	 *            irrelevant and may also be null
+	 * @param softDelete
+	 *            it is true if the client requires a soft deletion
+	 * @throws SyncSourceException
+	 */
+	@Override
+	public void removeSyncItem(SyncItemKey syncItemKey, Timestamp timestamp,
+			boolean softDelete) throws SyncSourceException {
+
+		try {
+
+			if (!softDelete) {
+				if (log.isTraceEnabled()) {
+					log.trace("PIMContactSyncSource remove the SyncItem");
+				}
+
+				this.manager.removeItem(syncItemKey.getKeyAsString());
+
+				if (log.isTraceEnabled()) {
+					log.trace("PIMContactSyncSource removed SyncItem");
+				}
+			}
+
+		} catch (EntityException e) {
+			log.error("Sync source error: could not delete item with key"
+					+ syncItemKey, e);
+			throw new SyncSourceException("Error deleting item. ", e);
+		}
+
+	}
+
+	@Override
+	public SyncItem getSyncItemFromId(SyncItemKey syncItemKey)
+			throws SyncSourceException {
+
+		String id = null;
+		SyncItem syncItem = null;
+
+		id = syncItemKey.getKeyAsString();
+
+		if (log.isTraceEnabled()) {
+			log.trace("PIMContactSyncSource get SyncItem from id " + id);
+		}
+
+		try {
+
+			// Retrieves the contact, wraps it in sync information and uses it
+			// to create a new SyncItem which is the return value of this method
+			syncItem = createSyncItem(id,
+					this.manager.getItem(id).getContact(), SyncItemState.NEW);
+
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error seeking SyncItem with ID: "
+					+ id, e);
+		}
+
+		return syncItem;
+
+	}
+
+	public boolean mergeSyncItems(SyncItemKey syncItemKey, SyncItem syncItem)
+			throws SyncSourceException {
+
+		try {
+
+			Contact contact = convert(getContentFromSyncItem(syncItem),
+					syncItem.getType());
+
+			boolean clientUpdateRequired = this.manager.mergeItems(syncItemKey
+					.getKeyAsString(), contact, syncItem.getTimestamp());
+
+			if (clientUpdateRequired) {
+				syncItem = getSyncItemFromId(syncItemKey);
+			}
+
+			// return clientUpdateRequired;
+			return true;
+
+		} catch (EntityException e) {
+
+			log.error("SyncSource error: a merge did not succeed.", e);
+			throw new SyncSourceException("Error merging SyncItem with key '"
+					+ syncItemKey + "' with SyncItem '" + syncItem + "'", e);
+		}
+	}
+
+	public void init() throws BeanInitializationException {
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all new contact IDs,
+	 * filtered according to a given time interval.
+	 * 
+	 * @param since
+	 *            the earlier limit of the time interval
+	 * @param to
+	 *            the later limit of the time interval
+	 * @return a SyncItemKey array
+	 */
+	@Override
+	public SyncItemKey[] getNewSyncItemKeys(Timestamp since, Timestamp to)
+			throws SyncSourceException {
+
+		saveSyncTiming(since, to);
+
+		try {
+			List idList = this.manager.getNewItems(since, to);
+			SyncItemKey[] keyList = new SyncItemKey[idList.size()];
+			for (int i = 0; i < idList.size(); i++) {
+				keyList[i] = new SyncItemKey(idList.get(i));
+			}
+			return keyList;
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error retrieving new item keys.", e);
+		}
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all deleted contact
+	 * IDs, filtered according to a given time interval.
+	 * 
+	 * @param since
+	 *            the earlier limit of the time interval
+	 * @param to
+	 *            the later limit of the time interval
+	 * @return a SyncItemKey array
+	 */
+	public SyncItemKey[] getUpdatedSyncItemKeys(Timestamp since, Timestamp to)
+			throws SyncSourceException {
+
+		saveSyncTiming(since, to);
+
+		try {
+			List idList = this.manager.getUpdatedItems(since, to);
+			SyncItemKey[] keyList = new SyncItemKey[idList.size()];
+			for (int i = 0; i < idList.size(); i++) {
+				keyList[i] = new SyncItemKey(idList.get(i));
+			}
+			return keyList;
+		} catch (EntityException e) {
+			throw new SyncSourceException(
+					"Error retrieving updated item keys.", e);
+		}
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all deleted contact
+	 * IDs, filtered according to a given time interval.
+	 * 
+	 * @param since
+	 *            the earlier limit of the time interval
+	 * @param to
+	 *            the later limit of the time interval
+	 * @return a SyncItemKey array
+	 */
+	@Override
+	public SyncItemKey[] getDeletedSyncItemKeys(Timestamp since, Timestamp to)
+			throws SyncSourceException {
+
+		saveSyncTiming(since, to);
+
+		try {
+// Alteracao - Incluido parametro this.getSourceURI() - emerson-faria.nobre@serpro.gov.br - 23/09/09
+			List idList = this.manager.getDeletedItems(since, to, this.getSourceURI());
+			SyncItemKey[] keyList = new SyncItemKey[idList.size()];
+			for (int i = 0; i < idList.size(); i++) {
+				keyList[i] = new SyncItemKey(idList.get(i));
+			}
+			return keyList;
+		} catch (EntityException e) {
+			throw new SyncSourceException(
+					"Error retrieving deleted item keys.", e);
+		}
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all contact IDs.
+	 * 
+	 * @return a SyncItemKey array
+	 */
+	@Override
+	public SyncItemKey[] getAllSyncItemKeys() throws SyncSourceException {
+
+		try {
+			List idList = this.manager.getAllItems();
+			SyncItemKey[] keyList = new SyncItemKey[idList.size()];
+			for (int i = 0; i < idList.size(); i++) {
+				keyList[i] = new SyncItemKey(idList.get(i));
+			}
+			return keyList;
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error retrieving all item keys. ", e);
+		}
+
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	private Contact sif2Contact(String sifc) throws EntityException {
+
+		if (log.isTraceEnabled()) {
+			StringBuilder sb = new StringBuilder(sifc.length() + 60);
+			sb.append("Converting: SIF-C => Contact").append("\nINPUT = {")
+					.append(sifc).append('}');
+			log.trace(sb.toString());
+		}
+
+		ByteArrayInputStream buffer = null;
+		SIFCParser parser = null;
+		Contact contact = null;
+		try {
+			contact = new Contact();
+			buffer = new ByteArrayInputStream(sifc.getBytes());
+			if ((sifc.getBytes()).length > 0) {
+				parser = new SIFCParser(buffer);
+				contact = parser.parse();
+			}
+		} catch (Exception e) {
+			throw new EntityException("Error converting SIF-C to Contact. ", e);
+		}
+
+		if (log.isTraceEnabled()) {
+			log.trace("Conversion done.");
+		}
+		return contact;
+	}
+
+	private Contact vcard2Contact(String vcard) throws EntityException {
+
+		if (log.isTraceEnabled()) {
+			StringBuilder sb = new StringBuilder(vcard.length() + 60);
+			sb.append("Converting: VCARD => Contact").append("\nINPUT = {")
+					.append(vcard).append('}');
+			log.trace(sb.toString());
+		}
+
+		ByteArrayInputStream buffer = null;
+		VcardParser parser = null;
+		Contact contact = null;
+		try {
+			contact = new Contact();
+
+			buffer = new ByteArrayInputStream(vcard.getBytes());
+			if ((vcard.getBytes()).length > 0) {
+				parser = new VcardParser(buffer,
+						this.deviceTimeZoneDescription, this.deviceCharset);
+				contact = parser.vCard();
+			}
+		} catch (Exception e) {
+			throw new EntityException("Error converting VCARD to Contact. ", e);
+		}
+
+		if (log.isTraceEnabled()) {
+			log.trace("Conversion done.");
+		}
+		return contact;
+	}
+
+	private String contact2sif(Contact contact) throws EntityException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("Converting: Contact => SIF-C");
+		}
+
+		String xml = null;
+		try {
+			// this.deviceTimeZone, this.deviceCharset
+			ContactToSIFC c2xml = new ContactToSIFC(null, null);
+			xml = c2xml.convert(contact);
+
+			if (log.isTraceEnabled()) {
+				log.trace("OUTPUT = {" + xml + "}. Conversion done.");
+			}
+		} catch (Exception e) {
+			throw new EntityException("Error converting Contact to SIF-C. ", e);
+		}
+		return xml;
+	}
+
+	private String contact2vcard(Contact contact) throws EntityException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("Converting: Contact => VCARD");
+		}
+
+		String vcard = null;
+		try {
+			ContactToVcard c2vc = new ContactToVcard(this.deviceTimeZone,
+					this.deviceCharset);
+			vcard = c2vc.convert(contact);
+
+			if (log.isTraceEnabled()) {
+				log.trace("OUTPUT = {" + vcard + "}. Conversion done.");
+			}
+		} catch (Exception e) {
+			throw new EntityException("Error converting Contact to VCARD. ", e);
+		}
+		return vcard;
+	}
+
+	/**
+	 * Create a new SyncItem from a Contact. The target contentType and status
+	 * are passed as arguments.
+	 * 
+	 * @param contact
+	 *            the Contact object representing the input information
+	 * @param contentType
+	 *            chosen among the TYPE array's elements
+	 * @param status
+	 * @throws EntityException
+	 *             if the content type is wrong or any problem occurs while
+	 *             creating a new SyncItem
+	 * @return a newly created SyncItem object
+	 */
+	private SyncItem createSyncItem(String id, Contact contact, char status)
+			throws EntityException {
+
+		String contentType = getInfo().getPreferredType().getType();
+
+		if (log.isTraceEnabled()) {
+			StringBuilder sb = new StringBuilder(100);
+			sb.append("PIMCalendarSyncSource - creating item with:").append(
+					"\n> id: ").append(id).append("\n> status: ")
+					.append(status).append("\n> content-type: ").append(
+							contentType);
+			log.trace(sb.toString());
+		}
+
+		SyncItem syncItem = null;
+		String stream = convert(contact, contentType);
+
+		try {
+			syncItem = new SyncItemImpl(this, id, status);
+		} catch (Exception e) {
+			throw new EntityException(e);
+		}
+
+		syncItem.setType(contentType);
+		syncItem.setContent(stream.getBytes());
+
+		if (log.isTraceEnabled()) {
+			log.trace("PIMContactSyncSource created SyncItem");
+		}
+
+		return syncItem;
+	}
+
+	/**
+	 * Converts a SyncItem to a Contact object, provided it represents a contact
+	 * item in VCard or SIF-C format.
+	 * 
+	 * @param syncItem
+	 * @throws EntityException
+	 *             if the contentType is wrong or the conversion attempt doesn't
+	 *             succeed.
+	 * @return a Contact object
+	 */
+	private Contact convert(SyncItem syncItem) throws EntityException {
+		return convert(getContentFromSyncItem(syncItem), syncItem.getType());
+	}
+
+	/**
+	 * Converts a contact in vCard or SIF-C format to a Contact object.
+	 * 
+	 * @param content
+	 *            as a String
+	 * @param contentType
+	 * @throws EntityException
+	 *             if the contentType is wrong or the conversion attempt doesn't
+	 *             succeed.
+	 * @return a Contact object
+	 */
+	private Contact convert(String content, String contentType)
+			throws EntityException {
+		// Finds out which target type is required
+		for (int i = 0; i < TYPE.length; i++) {
+			if (contentType.equals(TYPE[i])) { // Bingo!
+
+				// Uses the proper converter method
+				switch (i) {
+				case VCARD:
+					return vcard2Contact(content);
+				case SIFC:
+					return sif2Contact(content);
+				default:
+					throw new EntityException("Can't make a Contact "
+							+ "out of a " + TYPE[i] + "!");
+				}
+			}
+		}
+		throw new EntityException("Content type unknown: " + contentType);
+	}
+
+	/**
+	 * Converts a Contact back to a streamable (vCard, SIF-C) format.
+	 * 
+	 * @param contact
+	 * @param contentType
+	 * @throws EntityException
+	 *             if the contentType is wrong or the conversion attempt doesn't
+	 *             succeed.
+	 * @return the result in the required format
+	 */
+	private String convert(Contact contact, String contentType)
+			throws EntityException {
+
+		// Finds out which target type is required
+		for (int i = 0; i < TYPE.length; i++) {
+			if (contentType.equals(TYPE[i])) { // Bingo!
+
+				// Uses the proper converter method
+				switch (i) {
+				case VCARD:
+					return contact2vcard(contact);
+				case SIFC:
+					return contact2sif(contact);
+				default:
+					throw new EntityException("Can't make a " + TYPE[i]
+							+ "out of a Contact!");
+				}
+			}
+		}
+		throw new EntityException("Content type unknown: " + contentType);
+	}
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMCalendarSyncSource.java.original
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMCalendarSyncSource.java.original	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMCalendarSyncSource.java.original	(revision 2082)
@@ -0,0 +1,842 @@
+/**
+ * This class is a PIMSyncSource for ICal and VCal data, which could include
+ * either an Event or a Todo. NB: The Todo part is not currenlty implemented.
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.engine.source;
+
+import java.io.ByteArrayInputStream;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import br.com.prognus.psync.exception.EntityException;
+import br.com.prognus.psync.items.manager.PIMCalendarManager;
+import br.com.prognus.psync.items.model.CalendarWrapper;
+import br.com.prognus.psync.util.Def;
+
+import com.funambol.common.pim.calendar.Calendar;
+import com.funambol.common.pim.converter.BaseConverter;
+import com.funambol.common.pim.converter.CalendarToSIFE;
+import com.funambol.common.pim.converter.TaskToSIFT;
+import com.funambol.common.pim.converter.VCalendarConverter;
+import com.funambol.common.pim.converter.VComponentWriter;
+import com.funambol.common.pim.icalendar.ICalendarParser;
+import com.funambol.common.pim.model.VCalendar;
+import com.funambol.common.pim.sif.SIFCalendarParser;
+import com.funambol.common.pim.xvcalendar.XVCalendarParser;
+import com.funambol.framework.engine.SyncItem;
+import com.funambol.framework.engine.SyncItemImpl;
+import com.funambol.framework.engine.SyncItemKey;
+import com.funambol.framework.engine.SyncItemState;
+import com.funambol.framework.engine.source.SyncContext;
+import com.funambol.framework.engine.source.SyncSourceException;
+import com.funambol.framework.tools.beans.BeanInitializationException;
+import com.sun.org.apache.xerces.internal.impl.xs.SubstitutionGroupHandler;
+
+public class PIMCalendarSyncSource extends PIMSyncSource {
+
+	// ------------------------------------------------------------- Private
+	// data
+
+	private PIMCalendarManager manager;
+
+	// ---------------------------------------------------------------
+	// Properties
+
+	private Class entityType; // CalendarContent or one of its subclasses
+
+	public Class getEntityType() {
+		return entityType;
+	}
+
+	public void setEntityType(Class entityType) {
+		this.entityType = entityType;
+	}
+
+	// ----------------------------------------------------------- Public
+	// methods
+
+	public void beginSync(SyncContext context) {
+
+		this.manager = new PIMCalendarManager(JNDI_DATA_SOURCE_NAME, context
+				.getPrincipal());
+		super.manager = this.manager;
+		super.beginSync(context);
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing the ID(s) of the
+	 * twin(s) of a given calendar.
+	 *
+	 * @param syncItem
+	 *            the SyncItem representing the calendar whose twin(s) are
+	 *            looked for
+	 * @throws SyncSourceException
+	 * @return possibly, just one or no key should be in the array, but it can't
+	 *         be ruled out a priori that several keys get returned by this
+	 *         method
+	 */
+	public SyncItemKey[] getSyncItemKeysFromTwin(SyncItem syncItem)
+			throws SyncSourceException {
+
+		try {
+
+			Calendar calendar = convert(getContentFromSyncItem(syncItem),
+					syncItem.getType());
+
+			List idList = null;
+			idList = manager.getTwins(calendar);
+
+			SyncItemKey[] keyList = new SyncItemKey[idList.size()];
+			for (int i = 0; i < idList.size(); i++) {
+				keyList[i] = new SyncItemKey((String) idList.get(i));
+			}
+			return keyList;
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error retrieving twin item keys.", e);
+		}
+	}
+
+	/**
+	 * Adds a SyncItem object (representing a calendar).
+	 *
+	 * @param syncItem
+	 *            the SyncItem representing the calendar
+	 *
+	 * @return a newly created syncItem based on the input object but with its
+	 *         status set at SyncItemState.NEW and the GUID retrieved by the
+	 *         back-end
+	 */
+	public SyncItem addSyncItem(SyncItem syncItem) throws SyncSourceException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("PIMCalendarSyncSource addSyncItem begin");
+		}
+
+		Calendar c = null;
+		String content = null;
+
+		try {
+
+			content = getContentFromSyncItem(syncItem);
+			String contentType = syncItem.getType();
+
+			c = convert(content, contentType);
+			Timestamp ts = syncItem.getTimestamp();
+
+			// Adds the calendar, wraps it in sync information and uses it to
+			// create a new SyncItem which is the return value of this method
+			SyncItemImpl newSyncItem = new SyncItemImpl(this, // syncSource
+					manager.addItem(c, ts), // key
+					null, // mappedKey
+					SyncItemState.NEW, // state
+					content.getBytes(), // content
+					null, // format
+					contentType, // type
+					ts // timestamp
+			);
+
+			return newSyncItem;
+
+		} catch (Exception e) {
+			log.error("SyncSource error adding a new synchronization item.");
+			throw new SyncSourceException("Error adding the item " + syncItem,
+					e);
+		}
+	}
+
+	/**
+	 * Updates a SyncItem object (representing a calendar).
+	 *
+	 * @param syncItem
+	 *            the SyncItem representing the calendar
+	 *
+	 * @return a newly created syncItem based on the input object but with its
+	 *         status set at SyncItemState.UPDATED and the GUID retrieved by the
+	 *         back-end
+	 */
+	public SyncItem updateSyncItem(SyncItem syncItem)
+			throws SyncSourceException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("Updates a SyncItem from " + sourceURI);
+		}
+
+		Calendar c = null;
+		String content = null;
+
+		try {
+
+			String id = syncItem.getKey().getKeyAsString();
+			content = getContentFromSyncItem(syncItem);
+			String contentType = syncItem.getType();
+
+			c = convert(content, contentType);
+
+			// Modifies the calendar, wraps it in sync information and uses it
+			// to
+			// create a new SyncItem which is the return value of this method
+			SyncItemImpl newSyncItem = new SyncItemImpl(this, // syncSource
+					manager.updateItem(id, c, syncItem.getTimestamp()), // key
+					null, // mappedKey
+					SyncItemState.UPDATED, // state
+					content.getBytes(), // content
+					null, // format
+					contentType, // type
+					null // timestamp
+			);
+
+			return newSyncItem;
+
+		} catch (Exception e) {
+			log.error("SyncSource error updating a synchronization item.", e);
+			throw new SyncSourceException(
+					"Error updating the item " + syncItem, e);
+		}
+	}
+
+	/**
+	 * Deletes the item with a given syncItemKey.
+	 *
+	 * @param syncItemKey
+	 * @param timestamp
+	 *            in case of a soft deletion, this will be the registered moment
+	 *            of deletion; if a hard deletion is used, this field is
+	 *            irrelevant and may also be null
+	 * @param softDelete
+	 *            it is true if the client requires a soft deletion
+	 * @throws SyncSourceException
+	 */
+	public void removeSyncItem(SyncItemKey syncItemKey, Timestamp timestamp,
+			boolean softDelete) throws SyncSourceException {
+
+		try {
+
+			if (!softDelete) {
+				if (log.isTraceEnabled()) {
+					log.trace("PIMCalendarSyncSource remove the SyncItem "
+							+ syncItemKey.getKeyAsString());
+				}
+
+				manager.removeItem(syncItemKey.getKeyAsString());
+			}
+
+		} catch (EntityException e) {
+			log.error("Sync source error: could not delete item with key"
+					+ syncItemKey, e);
+			throw new SyncSourceException("Error deleting item. ", e);
+		}
+	}
+
+	public SyncItem getSyncItemFromId(SyncItemKey syncItemKey)
+			throws SyncSourceException {
+
+		String id = null;
+		SyncItem syncItem = null;
+
+		id = syncItemKey.getKeyAsString();
+		if (log.isTraceEnabled()) {
+			log.trace("PIMCalendarSyncSource get SyncItem from " + id);
+		}
+
+		try {
+			CalendarWrapper cw;
+			try {
+				cw = manager.getItem(id);
+			} catch (Exception e) {
+				return null;
+			}
+			// Retrieves the calendar, wraps it in sync information and uses it
+			// to create a new SyncItem which is the return value of this method
+			syncItem = createSyncItem(id, cw.getCalendar(), SyncItemState.NEW);
+
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error seeking SyncItem with ID: "
+					+ id, e);
+		}
+
+		return syncItem;
+	}
+
+	public boolean mergeSyncItems(SyncItemKey syncItemKey, SyncItem syncItem)
+			throws SyncSourceException {
+
+		try {
+			Calendar calendar = convert(getContentFromSyncItem(syncItem),
+					syncItem.getType());
+
+			boolean clientUpdateRequired = manager.mergeItems(syncItemKey
+					.getKeyAsString(), calendar, syncItem.getTimestamp());
+
+			if (clientUpdateRequired) {
+				syncItem = getSyncItemFromId(syncItemKey);
+			}
+
+			return true; //clientUpdateRequired 418
+
+		} catch (EntityException e) {
+
+			log.error("SyncSource error: a merge did not succeed.", e);
+			throw new SyncSourceException("Error merging SyncItem with ID "
+					+ syncItemKey.getKeyAsString() + "with SyncItem "
+					+ syncItem, e);
+		}
+	}
+
+	public void init() throws BeanInitializationException {
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all new calendar IDs,
+	 * filtered according to a given time interval.
+	 *
+	 * @param since
+	 *            the earlier limit of the time interval
+	 * @param to
+	 *            the later limit of the time interval
+	 * @return a SyncItemKey array
+	 */
+	public SyncItemKey[] getNewSyncItemKeys(Timestamp since, Timestamp to)
+			throws SyncSourceException {
+
+		saveSyncTiming(since, to);
+
+		try {
+			List idList = manager.getNewItems(since, to);
+			return extractKeyArrayFromIdList(idList);
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error retrieving new item keys.", e);
+		}
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all deleted calendar
+	 * IDs, filtered according to a given time interval.
+	 *
+	 * @param since
+	 *            the earlier limit of the time interval
+	 * @param to
+	 *            the later limit of the time interval
+	 * @return a SyncItemKey array
+	 */
+	public SyncItemKey[] getUpdatedSyncItemKeys(Timestamp since, Timestamp to)
+			throws SyncSourceException {
+
+		saveSyncTiming(since, to);
+
+		try {
+			List idList = manager.getUpdatedItems(since, to);
+			return extractKeyArrayFromIdList(idList);
+		} catch (EntityException e) {
+			throw new SyncSourceException(
+					"Error retrieving updated item keys.", e);
+		}
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all deleted calendar
+	 * IDs, filtered according to a given time interval.
+	 *
+	 * @param since
+	 *            the earlier limit of the time interval
+	 * @param to
+	 *            the later limit of the time interval
+	 * @return a SyncItemKey array
+	 */
+	public SyncItemKey[] getDeletedSyncItemKeys(Timestamp since, Timestamp to)
+			throws SyncSourceException {
+
+		saveSyncTiming(since, to);
+
+		try {
+			List idList = manager.getDeletedItems(since, to);
+			return extractKeyArrayFromIdList(idList);
+		} catch (EntityException e) {
+			throw new SyncSourceException(
+					"Error retrieving deleted item keys.", e);
+		}
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all calendar IDs.
+	 *
+	 * @return a SyncItemKey array
+	 */
+	public SyncItemKey[] getAllSyncItemKeys() throws SyncSourceException {
+
+		try {
+			List idList = manager.getAllItems();
+			return extractKeyArrayFromIdList(idList);
+
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error retrieving all item keys. ", e);
+		}
+	}
+
+	/**
+	 * Gets the status of the SyncItem with the given key.
+	 *
+	 * @param syncItemKey
+	 *            as a SyncItemKey object
+	 * @throws SyncSourceException
+	 * @return the status as a char
+	 */
+	public char getSyncItemStateFromId(SyncItemKey syncItemKey)
+			throws SyncSourceException {
+
+		String id = "N/A"; // default value for error tracking
+
+		try {
+
+			// Slow sync
+			// @todo Implement, depending on a syncMode check
+
+			// Fast sync
+			id = syncItemKey.getKeyAsString();
+			if (log.isTraceEnabled()) {
+				log
+						.trace("PIMCalendarSyncSource get SyncItem state from "
+								+ id);
+			}
+			char itemRawState = manager.getItemState(id, previousSyncTime);
+
+			if (itemRawState == Def.PIM_STATE_UNCHANGED) {
+				return SyncItemState.SYNCHRONIZED;
+			} else {
+				return itemRawState; // Def uses SyncItemState.* as constant
+				// values for N, D and U states
+			}
+		} catch (EntityException e) {
+			throw new SyncSourceException(
+					"Error getting the state of SyncItem " + "with ID " + id, e);
+		}
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	/**
+	 * Extracts the content from a syncItem.
+	 *
+	 * @param syncItem
+	 * @return as a String object (same as
+	 *         PIMSyncSource#getContentFromSyncItem(String), but trimmed)
+	 */
+	protected String getContentFromSyncItem(SyncItem syncItem) {
+
+		String raw = super.getContentFromSyncItem(syncItem);
+
+		return raw.trim();
+	}
+
+	private String hackFix(String text) {
+
+		String result, head, title, clas, description, tail, new_title;
+		int p_categories, p_classes, p_location, p_dstart, values1, values2, op1, op2, op3, op4, op5, op6, op7, op8, qt1, qt2;
+
+		qt1 = 0;
+		qt2 = 0;
+
+		// Pega as posicoes das tags
+		p_categories = text.indexOf("CATEGORIES");
+		p_classes = text.indexOf("CLASS");
+
+		p_location = text.indexOf("LOCATION");
+		p_dstart = text.indexOf("DTSTART");
+
+		// Pega as posicoes das varias formas da tag SUMMARY
+		op1 = text.indexOf("SUMMARY:");
+		op2 = text.indexOf("SUMMARY;CHARSET=UTF-8:");
+		op3 = text.indexOf("SUMMARY;ENCODING=QUOTED-PRINTABLE:");
+		op4 = text.indexOf("SUMMARY;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:");
+
+		// Pega as posicoes das varias formas da tag DESCRIPTION
+		op5 = text.indexOf("DESCRIPTION:");
+		op6 = text.indexOf("DESCRIPTION;CHARSET=UTF-8:");
+		op7 = text.indexOf("DESCRIPTION;ENCODING=QUOTED-PRINTABLE:");
+		op8 = text.indexOf("DESCRIPTION;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:");
+
+		qt1 += (op1 == -1) ? 0 : op1 + 8;
+		qt1 += (op2 == -1) ? 0 : op2 + 22;
+		qt1 += (op3 == -1) ? 0 : op3 + 34;
+		qt1 += (op4 == -1) ? 0 : op4 + 48;
+
+		qt2 += (op5 == -1) ? 0 : op5 + 12;
+		qt2 += (op6 == -1) ? 0 : op6 + 26;
+		qt2 += (op7 == -1) ? 0 : op7 + 38;
+		qt2 += (op8 == -1) ? 0 : op8 + 52;
+
+		values1 = (p_categories == -1) ? p_classes : p_categories;
+		values2 = (p_location == -1) ? p_dstart : p_location;
+
+		head = text.substring(0, qt1);
+		title = text.substring(qt1, values1);
+		clas = text.substring(values1, qt2);
+		description = text.substring(qt2, values2);
+		tail = text.substring(values2);
+
+		new_title = title.replaceAll("\r\n ", " ");
+
+		// Retira os enters no final do titulo
+		int title_t, title_bl;
+
+		while(true){
+			title_t = new_title.length();
+			title_bl = new_title.lastIndexOf("=0D=0A=\r\n");
+
+			if(title_bl != -1 && title_t == title_bl + 11){
+				new_title = new_title.substring(0, title_bl) + "\r\n";
+			} else {
+				break;
+			}
+		}
+
+		// Retira os enters no final da descricao
+		int description_t, description_bl;
+
+		while(true){
+			description_t = description.length();
+			description_bl = description.lastIndexOf("=0D=0A=\r\n");
+
+			if(description_bl != -1 && description_t == description_bl + 11){
+				description = description.substring(0, description_bl) + "\r\n";
+			} else {
+				break;
+			}
+		}
+
+		result = head + new_title + clas + description + tail;
+
+		return result;
+	}
+
+	private Calendar webCalendar2Calendar(String text, String vCalType)
+			throws EntityException {
+
+		try {
+			// BugFix XVCalendarParser
+			if (vCalType.equals(PIMSyncSource.TYPE[PIMSyncSource.VCAL])) {
+				text = hackFix(text);
+			}
+
+			ByteArrayInputStream buffer = new ByteArrayInputStream(text.getBytes());
+
+			VCalendar vcalendar;
+			String version;
+			String charset;
+
+			if (log.isTraceEnabled()) {
+				StringBuilder sb = new StringBuilder(text.length() + 60);
+				sb.append("Converting: ").append(vCalType).append(
+						" => Calendar ").append("\nINPUT = {").append(text)
+						.append('}');
+				log.trace(sb.toString());
+			}
+
+			if (vCalType.equals(PIMSyncSource.TYPE[PIMSyncSource.VCAL])) {
+				// vCalendar (1.0):
+				XVCalendarParser parser = new XVCalendarParser(buffer);
+				vcalendar = (VCalendar) parser.XVCalendar();
+				version = "1.0";
+				charset = BaseConverter.CHARSET_UTF7; // (versit spec)
+			} else {
+				// iCalendar (2.0):
+				ICalendarParser parser = new ICalendarParser(buffer);
+				vcalendar = (VCalendar) parser.ICalendar();
+				version = "2.0";
+				charset = BaseConverter.CHARSET_UTF8; // (RFC 2445)
+			}
+			if (deviceCharset != null) {
+				charset = deviceCharset; // overrides the default character
+				// set
+			}
+
+			String retrievedVersion = null;
+			if (vcalendar.getProperty("VERSION") != null) {
+				retrievedVersion = vcalendar.getProperty("VERSION").getValue();
+			}
+			vcalendar.addProperty("VERSION", version);
+			if (retrievedVersion == null) {
+				if (log.isTraceEnabled()) {
+					log.trace("No version property was found in the vCal/iCal "
+							+ "data: version set to " + version);
+				}
+			} else if (!retrievedVersion.equals(version)) {
+				if (log.isTraceEnabled()) {
+					log.trace("The version in the data was " + retrievedVersion
+							+ " but it's been changed to " + version);
+				}
+			}
+
+			VCalendarConverter vcf = new VCalendarConverter(deviceTimeZone,
+					charset);
+
+			Calendar c = null;
+			c = vcf.vcalendar2calendar(vcalendar);
+
+			if (log.isTraceEnabled()) {
+				log.trace("Conversion done.");
+			}
+
+			return c;
+
+		} catch (Exception e) {
+			throw new EntityException("Error converting " + vCalType
+					+ " to Calendar. ", e);
+		}
+	}
+
+	private String calendar2webCalendar(Calendar calendar, String vCalType)
+			throws EntityException {
+
+		try {
+
+			String charset;
+			if (vCalType.equals(PIMSyncSource.TYPE[PIMSyncSource.VCAL])) {
+				// vCalendar (1.0):
+				charset = BaseConverter.CHARSET_UTF7; // (versit spec)
+			} else {
+				// iCalendar (2.0):
+				charset = BaseConverter.CHARSET_UTF8; // (RFC 2445)
+			}
+			if (deviceCharset != null) {
+				charset = deviceCharset; // overrides the default character
+				// set
+			}
+
+			VCalendarConverter vcf = new VCalendarConverter(deviceTimeZone,
+					charset);
+
+			VCalendar vcalendar;
+			String vcal;
+
+			if (log.isTraceEnabled()) {
+				log.trace("Converting: Calendar => " + vCalType);
+			}
+
+			if (vCalType.equals(PIMSyncSource.TYPE[VCAL])) { // VCAL
+
+				vcalendar = vcf.calendar2vcalendar(calendar, true); // text/x-vcalendar
+
+			} else { // ICAL
+
+				vcalendar = vcf.calendar2vcalendar(calendar, false); // text/calendar
+			}
+
+			VComponentWriter writer = new VComponentWriter(
+					VComponentWriter.NO_FOLDING);
+			vcal = writer.toString(vcalendar);
+
+			if (log.isTraceEnabled()) {
+				log.trace("OUTPUT = {" + vcal + "}. Conversion done.");
+			}
+
+			return vcal;
+
+		} catch (Exception e) {
+			throw new EntityException("Error converting Calendar to "
+					+ vCalType, e);
+		}
+	}
+
+	/**
+	 * Create a new SyncItem from a Calendar. The status is passed as an
+	 * argument.
+	 *
+	 * @param calendar
+	 *            the Calendar object representing the input information
+	 * @param status
+	 * @throws EntityException
+	 *             if the content type is wrong or any problem occurs while
+	 *             creating a new SyncItem
+	 * @return a newly created SyncItem object
+	 */
+	private SyncItem createSyncItem(String id, Calendar calendar, char status)
+			throws EntityException {
+
+		String contentType = getInfo().getPreferredType().getType();
+
+		if (log.isTraceEnabled()) {
+			StringBuilder sb = new StringBuilder(100);
+			sb.append("PIMCalendarSyncSource - creating item with:").append(
+					"\n> id: ").append(id).append("\n> status: ")
+					.append(status).append("\n> content-type: ").append(
+							contentType);
+			log.trace(sb.toString());
+		}
+
+		SyncItem syncItem = null;
+		String stream = convert(calendar, contentType);
+
+		try {
+			syncItem = new SyncItemImpl(this, id, status);
+		} catch (Exception e) {
+			throw new EntityException(e);
+		}
+
+		syncItem.setType(contentType);
+		syncItem.setContent(stream.getBytes());
+		if (log.isTraceEnabled()) {
+			log.trace("PIMCalendarSyncSource created SyncItem");
+		}
+		return syncItem;
+	}
+
+	private SyncItemKey[] extractKeyArrayFromIdList(List idList) {
+
+		SyncItemKey[] keyList = new SyncItemKey[idList.size()];
+		for (int i = 0; i < idList.size(); i++) {
+			keyList[i] = new SyncItemKey((String) idList.get(i));
+		}
+		return keyList;
+	}
+
+	/**
+	 * Converts a calendar in vCalendar/iCalendar, SIF-E or SIF-T format to a
+	 * Calendar object.
+	 *
+	 * @param content
+	 *            as a String
+	 * @param contentType
+	 * @throws EntityException
+	 *             if the contentType is wrong or the conversion attempt doesn't
+	 *             succeed.
+	 * @return a Calendar object
+	 */
+	private Calendar convert(String content, String contentType)
+			throws EntityException {
+		// Finds out which target type is required
+		for (int i = 0; i < TYPE.length; i++) {
+			if (contentType.equals(TYPE[i])) {
+
+				// Uses the proper converter method
+				switch (i) {
+				case VCAL:
+				case ICAL:
+					return webCalendar2Calendar(content, contentType);
+				case SIFE:
+				case SIFT:
+					return sif2Calendar(content, contentType);
+				default:
+					throw new EntityException("Can't make a Contact "
+							+ "out of a " + TYPE[i] + "!");
+				}
+			}
+		}
+		throw new EntityException("Content type unknown: " + contentType);
+	}
+
+	/**
+	 * Converts a Calendar back to a streamable (vCalendar/iCalendar, SIF-E or
+	 * SIF-T) format.
+	 *
+	 * @param calendar
+	 * @param contentType
+	 * @throws EntityException
+	 *             if the contentType is wrong or the conversion attempt doesn't
+	 *             succeed.
+	 * @return the result in the required format
+	 */
+	private String convert(Calendar calendar, String contentType)
+			throws EntityException {
+
+		// Finds out which target type is required
+		for (int i = 0; i < TYPE.length; i++) {
+			if (contentType.equals(TYPE[i])) {
+
+				// Uses the proper converter method
+				switch (i) {
+				case VCAL:
+				case ICAL:
+					return calendar2webCalendar(calendar, contentType);
+				case SIFE:
+				case SIFT:
+					return calendar2sif(calendar, contentType);
+				default:
+					throw new EntityException("Can't make a " + TYPE[i]
+							+ "out of a Contact!");
+				}
+			}
+		}
+		throw new EntityException("Content type unknown: " + contentType);
+	}
+
+	private Calendar sif2Calendar(String xml, String sifType)
+			throws EntityException {
+
+		if (log.isTraceEnabled()) {
+			StringBuilder sb = new StringBuilder(xml.length() + 60);
+			sb.append("Converting: ").append(sifType).append(" => Calendar ")
+					.append("\nINPUT = {").append(xml).append('}');
+			log.trace(sb.toString());
+		}
+
+		ByteArrayInputStream buffer = null;
+		Calendar calendar = null;
+		try {
+			calendar = new Calendar();
+			buffer = new ByteArrayInputStream(xml.getBytes());
+			if ((xml.getBytes()).length > 0) {
+				SIFCalendarParser parser = new SIFCalendarParser(buffer);
+				calendar = parser.parse();
+			}
+		} catch (Exception e) {
+			throw new EntityException("Error converting " + sifType
+					+ " to Calendar. ", e);
+		}
+
+		if (log.isTraceEnabled()) {
+			log.trace("Conversion done.");
+		}
+		return calendar;
+	}
+
+	private String calendar2sif(Calendar calendar, String sifType)
+			throws EntityException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("Converting: Calendar => " + sifType);
+		}
+
+		String xml = null;
+		BaseConverter c2xml;
+		Object thing;
+
+		try {
+			if (sifType.equals(PIMSyncSource.TYPE[SIFE])) { // SIF-E
+				c2xml = new CalendarToSIFE(deviceTimeZone, deviceCharset);
+				thing = calendar;
+				// NB: A CalendarToSIFE converts a Calendar into a SIF-E
+			} else { // SIF-T
+				c2xml = new TaskToSIFT(deviceTimeZone, deviceCharset);
+				thing = calendar.getTask();
+				// NB: A TaskToSIFT converts just a Task into a SIF-T
+			}
+
+			xml = c2xml.convert(thing);
+
+			if (log.isTraceEnabled()) {
+				log.trace("OUTPUT = {" + xml + "}. Conversion done.");
+			}
+		} catch (Exception e) {
+			throw new EntityException(
+					"Error converting Calendar to " + sifType, e);
+		}
+		return xml;
+	}
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMSyncSource.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMSyncSource.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMSyncSource.java	(revision 2082)
@@ -0,0 +1,258 @@
+/**
+ *
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.engine.source;
+
+import java.io.Serializable;
+import java.sql.Timestamp;
+import java.util.TimeZone;
+
+import br.com.prognus.psync.exception.EntityException;
+import br.com.prognus.psync.items.manager.PIMEntityManager;
+import br.com.prognus.psync.util.Def;
+
+import com.funambol.framework.core.AlertCode;
+import com.funambol.framework.engine.SyncItem;
+import com.funambol.framework.engine.SyncItemKey;
+import com.funambol.framework.engine.SyncItemState;
+import com.funambol.framework.engine.source.AbstractSyncSource;
+import com.funambol.framework.engine.source.MergeableSyncSource;
+import com.funambol.framework.engine.source.SyncContext;
+import com.funambol.framework.engine.source.SyncSourceException;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+import com.funambol.framework.security.Sync4jPrincipal;
+import com.funambol.framework.server.Sync4jDevice;
+import com.funambol.framework.tools.beans.LazyInitBean;
+
+public abstract class PIMSyncSource extends AbstractSyncSource implements
+		MergeableSyncSource, Serializable, LazyInitBean {
+
+	// ----------------------------------------------------------------
+	// Constants
+
+	protected static final String JNDI_DATA_SOURCE_NAME = "jdbc/fnblds";
+
+	public static final int SIFC = 0; // To be used as index for SIF-Contact
+
+	public static final int SIFE = 1; // To be used as index for SIF-Event
+
+	public static final int SIFN = 2; // To be used as index for SIF-Note
+
+	public static final int SIFT = 3; // To be used as index for SIF-Task
+
+	public static final int VCARD = 4; // To be used as index for VCard
+
+	public static final int VCAL = 5; // To be used as index for VCal
+
+	public static final int ICAL = 6; // To be used as index for ICal
+
+	public static final int VNOTE = 7; // To be used as index for VNote
+
+	public static final String[] TYPE = { "text/x-s4j-sifc", // SIF-Contact
+			"text/x-s4j-sife", // SIF-Event
+			"text/x-s4j-sifn", // SIF-Note
+			"text/x-s4j-sift", // SIF-Task
+			"text/x-vcard", // VCard
+			"text/x-vcalendar", // VCal
+			"text/calendar", // ICal
+			"text/x-vnote", // VNote
+	};
+
+	// ----------------------------------------------------------- Protected
+	// data
+
+	protected static final FunambolLogger log = FunambolLoggerFactory
+			.getLogger(Def.LOGGER_NAME);
+
+	protected PIMEntityManager manager;
+
+	protected Sync4jPrincipal principal;
+
+	protected String userId;
+
+	protected int syncMode;
+
+	protected Timestamp lastSyncTime; // n-th synchronization
+
+	protected Timestamp previousSyncTime; // (n - 1)-th synchronization
+
+	protected TimeZone deviceTimeZone = null;
+
+	protected String deviceTimeZoneDescription = null;
+
+	protected String deviceCharset = null;
+
+	// ----------------------------------------------------------- Public
+	// methods
+
+	public void beginSync(SyncContext context) {
+
+		log.trace("PIMSyncSource beginSync start");
+
+		principal = context.getPrincipal();
+		userId = principal.getUsername();
+		syncMode = context.getSyncMode();
+
+		Sync4jDevice device = context.getPrincipal().getDevice();
+		String timezone = device.getTimeZone();
+		if (device.getConvertDate()) {
+			if (timezone != null && timezone.length() > 0) {
+				deviceTimeZoneDescription = timezone;
+				deviceTimeZone = TimeZone
+						.getTimeZone(deviceTimeZoneDescription);
+			}
+		}
+		deviceCharset = device.getCharset();
+
+		if (log.isTraceEnabled()) {
+			StringBuilder sb = new StringBuilder("Beginning sync with:");
+			sb.append("\n> syncMode            : ").append(syncMode);
+			sb.append("\n> principal           : ").append(principal);
+			sb.append("\n> deviceTimeZoneDescr.: ").append(
+					deviceTimeZoneDescription);
+			sb.append("\n> deviceTimeZone      : ").append(deviceTimeZone);
+			sb.append("\n> charset             : ").append(deviceCharset);
+
+			log.trace(sb.toString());
+		}
+
+		if (syncMode == AlertCode.REFRESH_FROM_CLIENT) {
+			if (log.isTraceEnabled()) {
+				log.trace("Performing REFRESH_FROM_CLIENT (203)");
+			}
+			removeAllSyncItems();
+		}
+
+		log.trace("PIMSyncSource beginSync end");
+	}
+
+	public void endSync() {
+	}
+
+	/**
+	 * Gets the status of the SyncItem with the given key.
+	 * 
+	 * @param syncItemKey
+	 *            as a SyncItemKey object
+	 * @throws SyncSourceException
+	 * @return the status as a char
+	 */
+	public char getSyncItemStateFromId(SyncItemKey syncItemKey)
+			throws SyncSourceException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("PIMSyncSource getSyncItemStateFromId begin");
+		}
+
+		String id = "N/A"; // default value for error tracking
+
+		try {
+
+			// Slow sync
+			// @todo Implement, depending on a syncMode check
+
+			// Fast sync
+			id = syncItemKey.getKeyAsString();
+			char itemRawState = manager.getItemState(id, previousSyncTime);
+
+			if (log.isTraceEnabled()) {
+				log.trace("PIMSyncSource getSyncItemStateFromId end");
+			}
+
+			if (itemRawState == Def.PIM_STATE_UNCHANGED) {
+				return SyncItemState.SYNCHRONIZED;
+			} else {
+				return itemRawState; // Def uses SyncItemState.* as constant
+				// values for N, D and U states
+			}
+		} catch (EntityException ee) {
+			throw new SyncSourceException(
+					"Error getting the state of SyncItem " + "with ID " + id,
+					ee);
+		}
+
+	}
+
+	/**
+	 * Not yet used. It just produces a log message.
+	 */
+	public void setOperationStatus(String operation, int statusCode,
+			SyncItemKey[] keys) {
+
+		if (log.isTraceEnabled()) {
+			StringBuilder message = new StringBuilder("Received status code '");
+			message.append(statusCode).append("' for a '").append(operation)
+					.append("' command for the following items: ");
+
+			for (int i = 0; i < keys.length; i++) {
+				message.append("\n> ").append(keys[i].getKeyAsString());
+			}
+
+			log.trace(message.toString());
+		}
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	/**
+	 * Extracts the content from a syncItem.
+	 * 
+	 * @param syncItem
+	 * @return as a String object
+	 */
+	protected String getContentFromSyncItem(SyncItem syncItem) {
+
+		byte[] itemContent = syncItem.getContent();
+
+		// Add content processing here, if needed
+
+		return new String(itemContent == null ? new byte[0] : itemContent);
+	}
+
+	protected void saveSyncTiming(Timestamp since, Timestamp to) {
+
+		if (log.isTraceEnabled()) {
+			if (!since.equals(previousSyncTime)) {
+				log.trace("PIMSyncSource sync timing updated to " + since
+						+ " / " + to);
+			}
+		}
+		this.previousSyncTime = since;
+		this.lastSyncTime = to;
+	}
+
+	protected void removeAllSyncItems() {
+
+		if (log.isTraceEnabled()) {
+			log.trace("Perform REFRESH_FROM_CLIENT (203) for user " + userId);
+		}
+
+		try {
+			manager.removeAllItems();
+		} catch (EntityException ee) {
+
+			if (log.isTraceEnabled()) {
+				log.trace("Error while performing REFRESH_FROM_CLIENT (203).",
+						ee);
+			}
+		}
+	}
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMCalendarSyncSource.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMCalendarSyncSource.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/engine/source/PIMCalendarSyncSource.java	(revision 2082)
@@ -0,0 +1,844 @@
+/**
+ * This class is a PIMSyncSource for ICal and VCal data, which could include
+ * either an Event or a Todo. NB: The Todo part is not currenlty implemented.
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.engine.source;
+
+import java.io.ByteArrayInputStream;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import br.com.prognus.psync.exception.EntityException;
+import br.com.prognus.psync.items.manager.PIMCalendarManager;
+import br.com.prognus.psync.items.model.CalendarWrapper;
+import br.com.prognus.psync.util.Def;
+
+import com.funambol.common.pim.calendar.CalendarContent;
+import com.funambol.common.pim.calendar.Reminder;
+import com.funambol.common.pim.utility.TimeUtils;
+import java.util.Date;
+
+import com.funambol.common.pim.calendar.Calendar;
+import com.funambol.common.pim.converter.BaseConverter;
+import com.funambol.common.pim.converter.CalendarToSIFE;
+import com.funambol.common.pim.converter.TaskToSIFT;
+import com.funambol.common.pim.converter.VCalendarConverter;
+import com.funambol.common.pim.converter.VComponentWriter;
+import com.funambol.common.pim.icalendar.ICalendarParser;
+import com.funambol.common.pim.model.VCalendar;
+import com.funambol.common.pim.sif.SIFCalendarParser;
+import com.funambol.common.pim.xvcalendar.XVCalendarParser;
+import com.funambol.framework.engine.SyncItem;
+import com.funambol.framework.engine.SyncItemImpl;
+import com.funambol.framework.engine.SyncItemKey;
+import com.funambol.framework.engine.SyncItemState;
+import com.funambol.framework.engine.source.SyncContext;
+import com.funambol.framework.engine.source.SyncSourceException;
+import com.funambol.framework.tools.beans.BeanInitializationException;
+import com.sun.org.apache.xerces.internal.impl.xs.SubstitutionGroupHandler;
+
+public class PIMCalendarSyncSource extends PIMSyncSource {
+
+	// ------------------------------------------------------------- Private
+	// data
+
+	private PIMCalendarManager manager;
+
+	// ---------------------------------------------------------------
+	// Properties
+
+	private Class entityType; // CalendarContent or one of its subclasses
+
+	public Class getEntityType() {
+		return entityType;
+	}
+
+	public void setEntityType(Class entityType) {
+		this.entityType = entityType;
+	}
+
+	// ----------------------------------------------------------- Public
+	// methods
+
+	public void beginSync(SyncContext context) {
+
+		this.manager = new PIMCalendarManager(JNDI_DATA_SOURCE_NAME, context
+				.getPrincipal());
+		super.manager = this.manager;
+		super.beginSync(context);
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing the ID(s) of the
+	 * twin(s) of a given calendar.
+	 *
+	 * @param syncItem
+	 *            the SyncItem representing the calendar whose twin(s) are
+	 *            looked for
+	 * @throws SyncSourceException
+	 * @return possibly, just one or no key should be in the array, but it can't
+	 *         be ruled out a priori that several keys get returned by this
+	 *         method
+	 */
+	public SyncItemKey[] getSyncItemKeysFromTwin(SyncItem syncItem)
+			throws SyncSourceException {
+
+		try {
+
+			Calendar calendar = convert(getContentFromSyncItem(syncItem),
+					syncItem.getType());
+
+			List idList = null;
+			idList = manager.getTwins(calendar);
+
+			SyncItemKey[] keyList = new SyncItemKey[idList.size()];
+			for (int i = 0; i < idList.size(); i++) {
+				keyList[i] = new SyncItemKey((String) idList.get(i));
+			}
+			return keyList;
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error retrieving twin item keys.", e);
+		}
+	}
+
+	/**
+	 * Adds a SyncItem object (representing a calendar).
+	 *
+	 * @param syncItem
+	 *            the SyncItem representing the calendar
+	 *
+	 * @return a newly created syncItem based on the input object but with its
+	 *         status set at SyncItemState.NEW and the GUID retrieved by the
+	 *         back-end
+	 */
+	public SyncItem addSyncItem(SyncItem syncItem) throws SyncSourceException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("PIMCalendarSyncSource addSyncItem begin");
+		}
+
+		Calendar c = null;
+		String content = null;
+
+		try {
+
+			content = getContentFromSyncItem(syncItem);
+			String contentType = syncItem.getType();
+
+			c = convert(content, contentType);
+			Timestamp ts = syncItem.getTimestamp();
+
+			// Adds the calendar, wraps it in sync information and uses it to
+			// create a new SyncItem which is the return value of this method
+			SyncItemImpl newSyncItem = new SyncItemImpl(this, // syncSource
+					manager.addItem(c, ts), // key
+					null, // mappedKey
+					SyncItemState.NEW, // state
+					content.getBytes(), // content
+					null, // format
+					contentType, // type
+					ts // timestamp
+			);
+
+			return newSyncItem;
+
+		} catch (Exception e) {
+			log.error("SyncSource error adding a new synchronization item.");
+			throw new SyncSourceException("Error adding the item " + syncItem,
+					e);
+		}
+	}
+
+	/**
+	 * Updates a SyncItem object (representing a calendar).
+	 *
+	 * @param syncItem
+	 *            the SyncItem representing the calendar
+	 *
+	 * @return a newly created syncItem based on the input object but with its
+	 *         status set at SyncItemState.UPDATED and the GUID retrieved by the
+	 *         back-end
+	 */
+	public SyncItem updateSyncItem(SyncItem syncItem)
+			throws SyncSourceException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("Updates a SyncItem from " + sourceURI);
+		}
+
+		Calendar c = null;
+		String content = null;
+
+		try {
+
+			String id = syncItem.getKey().getKeyAsString();
+			content = getContentFromSyncItem(syncItem);
+			String contentType = syncItem.getType();
+			c = convert(content, contentType);
+
+			// Modifies the calendar, wraps it in sync information and uses it
+			// to
+			// create a new SyncItem which is the return value of this method
+			SyncItemImpl newSyncItem = new SyncItemImpl(this, // syncSource
+					manager.updateItem(id, c, syncItem.getTimestamp()), // key
+					null, // mappedKey
+					SyncItemState.UPDATED, // state
+					content.getBytes(), // content
+					null, // format
+					contentType, // type
+					null // timestamp
+			);
+
+			return newSyncItem;
+
+		} catch (Exception e) {
+			log.error("SyncSource error updating a synchronization item.", e);
+			throw new SyncSourceException(
+					"Error updating the item " + syncItem, e);
+		}
+	}
+
+	/**
+	 * Deletes the item with a given syncItemKey.
+	 *
+	 * @param syncItemKey
+	 * @param timestamp
+	 *            in case of a soft deletion, this will be the registered moment
+	 *            of deletion; if a hard deletion is used, this field is
+	 *            irrelevant and may also be null
+	 * @param softDelete
+	 *            it is true if the client requires a soft deletion
+	 * @throws SyncSourceException
+	 */
+	public void removeSyncItem(SyncItemKey syncItemKey, Timestamp timestamp,
+			boolean softDelete) throws SyncSourceException {
+		try {
+
+			if (!softDelete) {
+				if (log.isTraceEnabled()) {
+					log.trace("PIMCalendarSyncSource remove the SyncItem "
+							+ syncItemKey.getKeyAsString());
+				}
+
+				manager.removeItem(syncItemKey.getKeyAsString());
+			}
+
+		} catch (EntityException e) {
+			log.error("Sync source error: could not delete item with key"
+					+ syncItemKey, e);
+			throw new SyncSourceException("Error deleting item. ", e);
+		}
+	}
+
+	public SyncItem getSyncItemFromId(SyncItemKey syncItemKey)
+			throws SyncSourceException {
+
+		String id = null;
+		SyncItem syncItem = null;
+
+		id = syncItemKey.getKeyAsString();
+		if (log.isTraceEnabled()) {
+			log.trace("PIMCalendarSyncSource get SyncItem from " + id);
+		}
+
+		try {
+			CalendarWrapper cw;
+			try {
+				cw = manager.getItem(id);
+			} catch (Exception e) {
+				return null;
+			}
+			// Retrieves the calendar, wraps it in sync information and uses it
+			// to create a new SyncItem which is the return value of this method
+			syncItem = createSyncItem(id, cw.getCalendar(), SyncItemState.NEW);
+
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error seeking SyncItem with ID: "
+					+ id, e);
+		}
+
+		return syncItem;
+	}
+
+	public boolean mergeSyncItems(SyncItemKey syncItemKey, SyncItem syncItem)
+			throws SyncSourceException {
+
+		try {
+			Calendar calendar = convert(getContentFromSyncItem(syncItem),
+					syncItem.getType());
+			boolean clientUpdateRequired = manager.mergeItems(syncItemKey
+					.getKeyAsString(), calendar, syncItem.getTimestamp());
+
+			if (clientUpdateRequired) {
+				syncItem = getSyncItemFromId(syncItemKey);
+			}
+
+			return true; //clientUpdateRequired 418
+
+		} catch (EntityException e) {
+
+			log.error("SyncSource error: a merge did not succeed.", e);
+			throw new SyncSourceException("Error merging SyncItem with ID "
+					+ syncItemKey.getKeyAsString() + "with SyncItem "
+					+ syncItem, e);
+		}
+	}
+
+	public void init() throws BeanInitializationException {
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all new calendar IDs,
+	 * filtered according to a given time interval.
+	 *
+	 * @param since
+	 *            the earlier limit of the time interval
+	 * @param to
+	 *            the later limit of the time interval
+	 * @return a SyncItemKey array
+	 */
+	public SyncItemKey[] getNewSyncItemKeys(Timestamp since, Timestamp to)
+			throws SyncSourceException {
+
+		saveSyncTiming(since, to);
+
+		try {
+			List idList = manager.getNewItems(since, to);
+			return extractKeyArrayFromIdList(idList);
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error retrieving new item keys.", e);
+		}
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all deleted calendar
+	 * IDs, filtered according to a given time interval.
+	 *
+	 * @param since
+	 *            the earlier limit of the time interval
+	 * @param to
+	 *            the later limit of the time interval
+	 * @return a SyncItemKey array
+	 */
+	public SyncItemKey[] getUpdatedSyncItemKeys(Timestamp since, Timestamp to)
+			throws SyncSourceException {
+
+		saveSyncTiming(since, to);
+
+		try {
+			List idList = manager.getUpdatedItems(since, to);
+			return extractKeyArrayFromIdList(idList);
+		} catch (EntityException e) {
+			throw new SyncSourceException(
+					"Error retrieving updated item keys.", e);
+		}
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all deleted calendar
+	 * IDs, filtered according to a given time interval.
+	 *
+	 * @param since
+	 *            the earlier limit of the time interval
+	 * @param to
+	 *            the later limit of the time interval
+	 * @return a SyncItemKey array
+	 */
+	public SyncItemKey[] getDeletedSyncItemKeys(Timestamp since, Timestamp to)
+			throws SyncSourceException {
+
+		saveSyncTiming(since, to);
+
+		try {
+// Alteracao - Incluido parametro this.getSourceURI() - emerson-faria.nobre@serpro.gov.br - 17/set/2009
+			List idList = manager.getDeletedItems(since, to, this.getSourceURI());
+			return extractKeyArrayFromIdList(idList);
+		} catch (EntityException e) {
+			throw new SyncSourceException(
+					"Error retrieving deleted item keys.", e);
+		}
+	}
+
+	/**
+	 * Makes an array of SyncItemKey objects representing all calendar IDs.
+	 *
+	 * @return a SyncItemKey array
+	 */
+	public SyncItemKey[] getAllSyncItemKeys() throws SyncSourceException {
+
+		try {
+			List idList = manager.getAllItems();
+			return extractKeyArrayFromIdList(idList);
+
+		} catch (EntityException e) {
+			throw new SyncSourceException("Error retrieving all item keys. ", e);
+		}
+	}
+
+	/**
+	 * Gets the status of the SyncItem with the given key.
+	 *
+	 * @param syncItemKey
+	 *            as a SyncItemKey object
+	 * @throws SyncSourceException
+	 * @return the status as a char
+	 */
+	public char getSyncItemStateFromId(SyncItemKey syncItemKey)
+			throws SyncSourceException {
+
+		String id = "N/A"; // default value for error tracking
+
+		try {
+
+			// Slow sync
+			// @todo Implement, depending on a syncMode check
+
+			// Fast sync
+			id = syncItemKey.getKeyAsString();
+			if (log.isTraceEnabled()) {
+				log
+						.trace("PIMCalendarSyncSource get SyncItem state from "
+								+ id);
+			}
+			char itemRawState = manager.getItemState(id, previousSyncTime);
+
+			if (itemRawState == Def.PIM_STATE_UNCHANGED) {
+				return SyncItemState.SYNCHRONIZED;
+			} else {
+				return itemRawState; // Def uses SyncItemState.* as constant
+				// values for N, D and U states
+			}
+		} catch (EntityException e) {
+			throw new SyncSourceException(
+					"Error getting the state of SyncItem " + "with ID " + id, e);
+		}
+	}
+
+	// ---------------------------------------------------------- Private
+	// methods
+
+	/**
+	 * Extracts the content from a syncItem.
+	 *
+	 * @param syncItem
+	 * @return as a String object (same as
+	 *         PIMSyncSource#getContentFromSyncItem(String), but trimmed)
+	 */
+	protected String getContentFromSyncItem(SyncItem syncItem) {
+
+		String raw = super.getContentFromSyncItem(syncItem);
+
+		return raw.trim();
+	}
+
+	private String hackFix(String text) {
+
+		String result, head, title, clas, description, tail, new_title;
+		int p_categories, p_classes, p_location, p_dstart, values1, values2, op1, op2, op3, op4, op5, op6, op7, op8, qt1, qt2;
+
+		qt1 = 0;
+		qt2 = 0;
+
+		// Pega as posicoes das tags
+		p_categories = text.indexOf("CATEGORIES");
+		p_classes = text.indexOf("CLASS");
+
+		p_location = text.indexOf("LOCATION");
+		p_dstart = text.indexOf("DTSTART");
+
+		// Pega as posicoes das varias formas da tag SUMMARY
+		op1 = text.indexOf("SUMMARY:");
+		op2 = text.indexOf("SUMMARY;CHARSET=UTF-8:");
+		op3 = text.indexOf("SUMMARY;ENCODING=QUOTED-PRINTABLE:");
+		op4 = text.indexOf("SUMMARY;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:");
+
+		// Pega as posicoes das varias formas da tag DESCRIPTION
+		op5 = text.indexOf("DESCRIPTION:");
+		op6 = text.indexOf("DESCRIPTION;CHARSET=UTF-8:");
+		op7 = text.indexOf("DESCRIPTION;ENCODING=QUOTED-PRINTABLE:");
+		op8 = text.indexOf("DESCRIPTION;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:");
+
+		qt1 += (op1 == -1) ? 0 : op1 + 8;
+		qt1 += (op2 == -1) ? 0 : op2 + 22;
+		qt1 += (op3 == -1) ? 0 : op3 + 34;
+		qt1 += (op4 == -1) ? 0 : op4 + 48;
+
+		qt2 += (op5 == -1) ? 0 : op5 + 12;
+		qt2 += (op6 == -1) ? 0 : op6 + 26;
+		qt2 += (op7 == -1) ? 0 : op7 + 38;
+		qt2 += (op8 == -1) ? 0 : op8 + 52;
+
+		values1 = (p_categories == -1) ? p_classes : p_categories;
+		values2 = (p_location == -1) ? p_dstart : p_location;
+
+		head = text.substring(0, qt1);
+		title = text.substring(qt1, values1);
+		clas = text.substring(values1, qt2);
+		description = text.substring(qt2, values2);
+		tail = text.substring(values2);
+
+		new_title = title.replaceAll("\r\n ", " ");
+
+		// Retira os enters no final do titulo
+		int title_t, title_bl;
+
+		while(true){
+			title_t = new_title.length();
+			title_bl = new_title.lastIndexOf("=0D=0A=\r\n");
+
+			if(title_bl != -1 && title_t == title_bl + 11){
+				new_title = new_title.substring(0, title_bl) + "\r\n";
+			} else {
+				break;
+			}
+		}
+
+		// Retira os enters no final da descricao
+		int description_t, description_bl;
+
+		while(true){
+			description_t = description.length();
+			description_bl = description.lastIndexOf("=0D=0A=\r\n");
+
+			if(description_bl != -1 && description_t == description_bl + 11){
+				description = description.substring(0, description_bl) + "\r\n";
+			} else {
+				break;
+			}
+		}
+
+		result = head + new_title + clas + description + tail;
+
+		return result;
+	}
+
+	private Calendar webCalendar2Calendar(String text, String vCalType)
+			throws EntityException {
+
+		try {
+			// BugFix XVCalendarParser
+			//if (vCalType.equals(PIMSyncSource.TYPE[PIMSyncSource.VCAL])) {
+			//	text = hackFix(text);
+			//}
+
+			ByteArrayInputStream buffer = new ByteArrayInputStream(text.getBytes());
+
+			VCalendar vcalendar;
+			String version;
+			String charset;
+
+			if (log.isTraceEnabled()) {
+				StringBuilder sb = new StringBuilder(text.length() + 60);
+				sb.append("Converting: ").append(vCalType).append(
+						" => Calendar ").append("\nINPUT = {").append(text)
+						.append('}');
+				log.trace(sb.toString());
+			}
+
+			if (vCalType.equals(PIMSyncSource.TYPE[PIMSyncSource.VCAL])) {
+				// vCalendar (1.0):
+				XVCalendarParser parser = new XVCalendarParser(buffer);
+				vcalendar = (VCalendar) parser.XVCalendar();
+				version = "1.0";
+				charset = BaseConverter.CHARSET_UTF7; // (versit spec)
+			} else {
+				// iCalendar (2.0):
+				ICalendarParser parser = new ICalendarParser(buffer);
+				vcalendar = (VCalendar) parser.ICalendar();
+				version = "2.0";
+				charset = BaseConverter.CHARSET_UTF8; // (RFC 2445)
+			}
+			if (deviceCharset != null) {
+				charset = deviceCharset; // overrides the default character
+				// set
+			}
+
+			String retrievedVersion = null;
+			if (vcalendar.getProperty("VERSION") != null) {
+				retrievedVersion = vcalendar.getProperty("VERSION").getValue();
+			}
+			vcalendar.addProperty("VERSION", version);
+			if (retrievedVersion == null) {
+				if (log.isTraceEnabled()) {
+					log.trace("No version property was found in the vCal/iCal "
+							+ "data: version set to " + version);
+				}
+			} else if (!retrievedVersion.equals(version)) {
+				if (log.isTraceEnabled()) {
+					log.trace("The version in the data was " + retrievedVersion
+							+ " but it's been changed to " + version);
+				}
+			}
+
+			VCalendarConverter vcf = new VCalendarConverter(deviceTimeZone,
+					charset);
+
+			Calendar c = null;
+			c = vcf.vcalendar2calendar(vcalendar);
+			if (log.isTraceEnabled()) {
+				log.trace("Conversion done.");
+			}
+
+			return c;
+
+		} catch (Exception e) {
+			throw new EntityException("Error converting " + vCalType
+					+ " to Calendar. ", e);
+		}
+	}
+
+	private String calendar2webCalendar(Calendar calendar, String vCalType)
+			throws EntityException {
+
+		try {
+
+			String charset;
+			if (vCalType.equals(PIMSyncSource.TYPE[PIMSyncSource.VCAL])) {
+				// vCalendar (1.0):
+				charset = BaseConverter.CHARSET_UTF7; // (versit spec)
+			} else {
+				// iCalendar (2.0):
+				charset = BaseConverter.CHARSET_UTF8; // (RFC 2445)
+			}
+			if (deviceCharset != null) {
+				charset = deviceCharset; // overrides the default character
+				// set
+			}
+
+			VCalendarConverter vcf = new VCalendarConverter(deviceTimeZone,
+					charset);
+
+			VCalendar vcalendar;
+			String vcal;
+
+			if (log.isTraceEnabled()) {
+				log.trace("Converting: Calendar => " + vCalType);
+			}
+
+			if (vCalType.equals(PIMSyncSource.TYPE[VCAL])) { // VCAL
+
+				vcalendar = vcf.calendar2vcalendar(calendar, true); // text/x-vcalendar
+
+			} else { // ICAL
+
+				vcalendar = vcf.calendar2vcalendar(calendar, false); // text/calendar
+			}
+
+			VComponentWriter writer = new VComponentWriter(
+					VComponentWriter.NO_FOLDING);
+			vcal = writer.toString(vcalendar);
+
+			if (log.isTraceEnabled()) {
+				log.trace("OUTPUT = {" + vcal + "}. Conversion done.");
+			}
+
+			return vcal;
+
+		} catch (Exception e) {
+			throw new EntityException("Error converting Calendar to "
+					+ vCalType, e);
+		}
+	}
+
+	/**
+	 * Create a new SyncItem from a Calendar. The status is passed as an
+	 * argument.
+	 *
+	 * @param calendar
+	 *            the Calendar object representing the input information
+	 * @param status
+	 * @throws EntityException
+	 *             if the content type is wrong or any problem occurs while
+	 *             creating a new SyncItem
+	 * @return a newly created SyncItem object
+	 */
+	private SyncItem createSyncItem(String id, Calendar calendar, char status)
+			throws EntityException {
+
+		String contentType = getInfo().getPreferredType().getType();
+
+		if (log.isTraceEnabled()) {
+			StringBuilder sb = new StringBuilder(100);
+			sb.append("PIMCalendarSyncSource - creating item with:").append(
+					"\n> id: ").append(id).append("\n> status: ")
+					.append(status).append("\n> content-type: ").append(
+							contentType);
+			log.trace(sb.toString());
+		}
+
+		SyncItem syncItem = null;
+		String stream = convert(calendar, contentType);
+
+		try {
+			syncItem = new SyncItemImpl(this, id, status);
+		} catch (Exception e) {
+			throw new EntityException(e);
+		}
+
+		syncItem.setType(contentType);
+		syncItem.setContent(stream.getBytes());
+		if (log.isTraceEnabled()) {
+			log.trace("PIMCalendarSyncSource created SyncItem");
+		}
+		return syncItem;
+	}
+
+	private SyncItemKey[] extractKeyArrayFromIdList(List idList) {
+
+		SyncItemKey[] keyList = new SyncItemKey[idList.size()];
+		for (int i = 0; i < idList.size(); i++) {
+			keyList[i] = new SyncItemKey((String) idList.get(i));
+		}
+		return keyList;
+	}
+
+	/**
+	 * Converts a calendar in vCalendar/iCalendar, SIF-E or SIF-T format to a
+	 * Calendar object.
+	 *
+	 * @param content
+	 *            as a String
+	 * @param contentType
+	 * @throws EntityException
+	 *             if the contentType is wrong or the conversion attempt doesn't
+	 *             succeed.
+	 * @return a Calendar object
+	 */
+	private Calendar convert(String content, String contentType)
+			throws EntityException {
+		// Finds out which target type is required
+		for (int i = 0; i < TYPE.length; i++) {
+			if (contentType.equals(TYPE[i])) {
+
+				// Uses the proper converter method
+				switch (i) {
+				case VCAL:
+				case ICAL:
+					return webCalendar2Calendar(content, contentType);
+				case SIFE:
+				case SIFT:
+					return sif2Calendar(content, contentType);
+				default:
+					throw new EntityException("Can't make a Contact "
+							+ "out of a " + TYPE[i] + "!");
+				}
+			}
+		}
+		throw new EntityException("Content type unknown: " + contentType);
+	}
+
+	/**
+	 * Converts a Calendar back to a streamable (vCalendar/iCalendar, SIF-E or
+	 * SIF-T) format.
+	 *
+	 * @param calendar
+	 * @param contentType
+	 * @throws EntityException
+	 *             if the contentType is wrong or the conversion attempt doesn't
+	 *             succeed.
+	 * @return the result in the required format
+	 */
+	private String convert(Calendar calendar, String contentType)
+			throws EntityException {
+
+		// Finds out which target type is required
+		for (int i = 0; i < TYPE.length; i++) {
+			if (contentType.equals(TYPE[i])) {
+
+				// Uses the proper converter method
+				switch (i) {
+				case VCAL:
+				case ICAL:
+					return calendar2webCalendar(calendar, contentType);
+				case SIFE:
+				case SIFT:
+					return calendar2sif(calendar, contentType);
+				default:
+					throw new EntityException("Can't make a " + TYPE[i]
+							+ "out of a Contact!");
+				}
+			}
+		}
+		throw new EntityException("Content type unknown: " + contentType);
+	}
+
+	private Calendar sif2Calendar(String xml, String sifType)
+			throws EntityException {
+
+		if (log.isTraceEnabled()) {
+			StringBuilder sb = new StringBuilder(xml.length() + 60);
+			sb.append("Converting: ").append(sifType).append(" => Calendar ")
+					.append("\nINPUT = {").append(xml).append('}');
+			log.trace(sb.toString());
+		}
+
+		ByteArrayInputStream buffer = null;
+		Calendar calendar = null;
+		try {
+			calendar = new Calendar();
+			buffer = new ByteArrayInputStream(xml.getBytes());
+			if ((xml.getBytes()).length > 0) {
+				SIFCalendarParser parser = new SIFCalendarParser(buffer);
+				calendar = parser.parse();
+			}
+		} catch (Exception e) {
+			throw new EntityException("Error converting " + sifType
+					+ " to Calendar. ", e);
+		}
+
+		if (log.isTraceEnabled()) {
+			log.trace("Conversion done.");
+		}
+		return calendar;
+	}
+
+	private String calendar2sif(Calendar calendar, String sifType)
+			throws EntityException {
+
+		if (log.isTraceEnabled()) {
+			log.trace("Converting: Calendar => " + sifType);
+		}
+
+		String xml = null;
+		BaseConverter c2xml;
+		Object thing;
+
+		try {
+			if (sifType.equals(PIMSyncSource.TYPE[SIFE])) { // SIF-E
+				c2xml = new CalendarToSIFE(deviceTimeZone, deviceCharset);
+				thing = calendar;
+				// NB: A CalendarToSIFE converts a Calendar into a SIF-E
+			} else { // SIF-T
+				c2xml = new TaskToSIFT(deviceTimeZone, deviceCharset);
+				thing = calendar.getTask();
+				// NB: A TaskToSIFT converts just a Task into a SIF-T
+			}
+
+			xml = c2xml.convert(thing);
+
+			if (log.isTraceEnabled()) {
+				log.trace("OUTPUT = {" + xml + "}. Conversion done.");
+			}
+		} catch (Exception e) {
+			throw new EntityException(
+					"Error converting Calendar to " + sifType, e);
+		}
+		return xml;
+	}
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MyMergeableSyncSource.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MyMergeableSyncSource.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MyMergeableSyncSource.java	(revision 2082)
@@ -0,0 +1,89 @@
+/*
+ * Funambol is a mobile platform developed by Funambol, Inc.
+ * Copyright (C) 2008 Funambol, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Affero General Public License version 3 as published by
+ * the Free Software Foundation with the addition of the following permission
+ * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
+ * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
+ * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses or write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA.
+ *
+ * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
+ * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU Affero General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU Affero General Public License
+ * version 3, these Appropriate Legal Notices must retain the display of the
+ * "Powered by Funambol" logo. If the display of the logo is not reasonably
+ * feasible for technical reasons, the Appropriate Legal Notices must display
+ * the words "Powered by Funambol".
+ */
+
+package br.com.prognus.psync;
+
+import com.funambol.framework.engine.source.*;
+
+import com.funambol.framework.engine.SyncItem;
+import com.funambol.framework.engine.SyncItemKey;
+import com.funambol.framework.engine.source.*;
+
+
+/**
+ * This is a simple MergeableSyncSource prototype. It implements the methods 
+ * required by the MergeableSyncSource interface. A mergeable SyncSource is a
+ * sync source where conflicts are resolved when possible merging the data
+ * received by the server with the data retrieved by the data store.
+ *
+ */
+public class MyMergeableSyncSource extends MySyncSource
+    implements MergeableSyncSource {
+
+    // --------------------------------------------------------------- Constants
+
+    // ------------------------------------------------------------ Private data
+    
+    // -------------------------------------------------------------- Properties
+
+    // ------------------------------------------------------------ Constructors
+
+    public MyMergeableSyncSource() {
+    }
+
+    // ---------------------------------------------------------- Public Methods
+
+    /**
+     * Merges server item and client item.
+     *
+     * @param key the item's key on the server
+     * @param clientItem the item on the client
+     *
+     * @throws SyncSourceException
+     *
+     * @return true is the content of the client item is changed
+     */
+    public boolean mergeSyncItems(SyncItemKey key,
+                                  SyncItem    clientItem) 
+    throws SyncSourceException {
+        //
+        // Put here your code to merge the item in the data store with the
+        // item received by the client
+        //
+        log.info("mergeSyncItems()");
+        return false;
+    }
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MySynclet.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MySynclet.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MySynclet.java	(revision 2082)
@@ -0,0 +1,116 @@
+/*
+ * Funambol is a mobile platform developed by Funambol, Inc.
+ * Copyright (C) 2008 Funambol, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Affero General Public License version 3 as published by
+ * the Free Software Foundation with the addition of the following permission
+ * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
+ * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
+ * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses or write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA.
+ *
+ * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
+ * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU Affero General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU Affero General Public License
+ * version 3, these Appropriate Legal Notices must retain the display of the
+ * "Powered by Funambol" logo. If the display of the logo is not reasonably
+ * feasible for technical reasons, the Appropriate Legal Notices must display
+ * the words "Powered by Funambol".
+ */
+
+package br.com.prognus;
+
+
+import com.funambol.framework.core.SyncML;
+import com.funambol.framework.core.Sync4jException;
+import com.funambol.framework.engine.pipeline.InputMessageProcessor;
+import com.funambol.framework.engine.pipeline.OutputMessageProcessor;
+import com.funambol.framework.engine.pipeline.MessageProcessingContext;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+import com.funambol.framework.tools.SyncMLUtil;
+
+/**
+ * This class just logs input and output messages. This synclet can then be
+ * inserted in any position in a pipeline in order to trace how a message is
+ * changed during the pipeline processing. It is at the same time an input and
+ * an output pipeline component.<br>
+ * Logging is done at INFO level
+ */
+public class MySynclet implements InputMessageProcessor, OutputMessageProcessor {
+    
+    // --------------------------------------------------------------- Constants
+
+    public static final String LOG_NAME = "myconnector";
+
+    // ------------------------------------------------------------ Private data
+
+    private static final FunambolLogger log = FunambolLoggerFactory.getLogger(LOG_NAME);
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * Logs the input message and context
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     *
+     * @throws Sync4jException
+     */
+    public void preProcessMessage(MessageProcessingContext processingContext,
+                                  SyncML                   message)
+    throws Sync4jException {
+        if (log.isInfoEnabled()) {
+            log.info("--------------------------------------------------------------------------------");
+            log.info("Input message processing context"                                                );
+            log.info("                                                                                ");
+            log.info(processingContext.toString()                                                      );
+            log.info("--------------------------------------------------------------------------------");
+            log.info("Input message"                                                                   );
+            log.info("                                                                                ");
+            log.info(SyncMLUtil.toXML(message)                                                               );
+            log.info("--------------------------------------------------------------------------------");
+        }
+    }
+
+    /**
+     * Logs the output message and context
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     *
+     * @throws Sync4jException
+     */
+    public void postProcessMessage(MessageProcessingContext processingContext,
+                                   SyncML                   message)
+    throws Sync4jException {
+        if (log.isInfoEnabled()) {
+            log.info("--------------------------------------------------------------------------------");
+            log.info("Output message processing context"                                               );
+            log.info("                                                                                ");
+            log.info(processingContext.toString()                                                      );
+            log.info("--------------------------------------------------------------------------------");
+            log.info("Output message"                                                                  );
+            log.info("                                                                                ");
+            log.info(SyncMLUtil.toXML(message)                                                               );
+            log.info("--------------------------------------------------------------------------------");
+        }
+    }
+
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/admin/PIMCalendarSyncSourceConfigPanel.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/admin/PIMCalendarSyncSourceConfigPanel.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/admin/PIMCalendarSyncSourceConfigPanel.java	(revision 2082)
@@ -0,0 +1,333 @@
+/**
+ * This class does something.
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.admin;
+
+import java.awt.Dimension;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JCheckBox;
+
+import br.com.prognus.psync.engine.source.PIMCalendarSyncSource;
+import br.com.prognus.psync.engine.source.PIMSyncSource;
+
+import com.funambol.admin.AdminException;
+import com.funambol.common.pim.calendar.CalendarContent;
+import com.funambol.common.pim.calendar.Event;
+import com.funambol.common.pim.calendar.Task;
+import com.funambol.framework.engine.source.ContentType;
+import com.funambol.framework.engine.source.SyncSource;
+import com.funambol.framework.engine.source.SyncSourceInfo;
+
+public class PIMCalendarSyncSourceConfigPanel extends PIMSyncSourceConfigPanel
+		implements Serializable {
+
+	// --------------------------------------------------------------- Constants
+	protected static final String PANEL_NAME = "Edit PIM Calendar SyncSource";
+
+	protected static final String TYPE_LABEL_SIFE = "SIF-E";
+
+	protected static final String TYPE_LABEL_SIFT = "SIF-T";
+
+	protected static final String TYPE_LABEL_VCAL = "VCal";
+
+	protected static final String TYPE_LABEL_ICAL = "ICal";
+
+	protected static final String TYPE_SIFE = "text/x-s4j-sife";
+
+	protected static final String TYPE_SIFT = "text/x-s4j-sift";
+
+	protected static final String TYPE_VCAL = "text/x-vcalendar";
+
+	protected static final String TYPE_ICAL = "text/calendar";
+
+	protected static final String VERSION_SIFE = "1.0";
+
+	protected static final String VERSION_SIFT = "1.0";
+
+	protected static final String VERSION_VCAL = "1.0";
+
+	protected static final String VERSION_ICAL = "2.0";
+
+	// ---------------------------------------------------------- Protected data
+	protected JCheckBox eventValue;
+
+	protected JCheckBox taskValue;
+
+	// ------------------------------------------------------------ Constructors
+
+	/** Creates a new instance of PIMCalendarSyncSourceConfigPanel. */
+	public PIMCalendarSyncSourceConfigPanel() {
+		super();
+
+		// this.add(eventLabel, null);
+		this.add(eventValue, null);
+		// this.add(taskLabel, null);
+		this.add(taskValue, null);
+
+		typeCombo.addItemListener(new ItemListener() {
+			public void itemStateChanged(ItemEvent e) {
+				updateEntityTypeCheckBoxes();
+			}
+		});
+
+	}
+
+	// ---------------------------------------------------------- Public methods
+
+	/**
+	 * Updates the form
+	 */
+	public void updateForm() {
+
+		if (!(getSyncSource() instanceof PIMCalendarSyncSource)) {
+			notifyError(new AdminException(
+					"This is not a PIMCalendarSyncSource! "
+							+ "Unable to process SyncSource values."));
+			return;
+		}
+
+		super.updateForm();
+
+		updateEntityTypeCheckBoxes();
+	}
+
+	/**
+	 * Set preferredSize of the panel
+	 * 
+	 * @return preferredSize of the panel
+	 */
+	public Dimension getPreferredSize() {
+		return new Dimension(525, 310);
+	}
+
+	// ------------------------------------------------------- Protected methods
+
+	/**
+	 * Adds extra components just above the confirm button.
+	 * 
+	 * @param x
+	 *            horizontal position
+	 * @param y
+	 *            vertical position
+	 * @param xGap
+	 *            standard horizontal gap
+	 * @param yGap
+	 *            standard vertical gap
+	 * @return the new vertical position
+	 */
+	protected int addExtraComponents(int x, int y, int xGap, int yGap) {
+
+		eventValue = new JCheckBox("Events");
+		taskValue = new JCheckBox("Tasks");
+
+		eventValue.setFont(defaultFont);
+		eventValue.setSelected(true);
+		eventValue.setBounds(170, y, 100, 25);
+		eventValue.setEnabled(true);
+
+		x += xGap; // Shift a bit to the right
+
+		taskValue.setFont(defaultFont);
+		taskValue.setSelected(true);
+		taskValue.setBounds(170 + xGap, y, 100, 25);
+		taskValue.setEnabled(true);
+
+		return y + yGap;
+	}
+
+	/**
+	 * Updates entities checkboxes according to the selected type
+	 */
+	protected void updateEntityTypeCheckBoxes() {
+		if (isSIFSelected()) {
+			eventValue.setSelected(areEventsAllowed());
+			taskValue.setSelected(areTasksAllowed());
+
+			eventValue.setEnabled(false);
+			taskValue.setEnabled(false);
+		} else {
+			PIMCalendarSyncSource syncSource = (PIMCalendarSyncSource) getSyncSource();
+			boolean events = false;
+			boolean tasks = false;
+			if (syncSource.getEntityType() == null
+					|| syncSource.getEntityType().isAssignableFrom(Event.class)) {
+				events = true;
+			}
+			if (syncSource.getEntityType() == null
+					|| syncSource.getEntityType().isAssignableFrom(Task.class)) {
+				tasks = true;
+			}
+			eventValue.setSelected(events);
+			taskValue.setSelected(tasks);
+
+			eventValue.setEnabled(true);
+			taskValue.setEnabled(true);
+		}
+	}
+
+	/**
+	 * Validates the values selected.
+	 * 
+	 * @throws java.lang.IllegalArgumntException
+	 *             if there is an invalid value
+	 */
+	protected void validateValues() throws IllegalArgumentException {
+		super.validateValues();
+
+		if (!eventValue.isSelected() && !taskValue.isSelected()) {
+			throw new IllegalArgumentException(
+					"Please check at least one between 'Events' and 'Tasks'.");
+		}
+	}
+
+	/**
+	 * Sets the syncsource with the inserted values
+	 */
+	protected void getValues() {
+		super.getValues();
+
+		if ((eventValue == null) || (taskValue == null)) {
+			return;
+		}
+
+		PIMCalendarSyncSource syncSource = (PIMCalendarSyncSource) getSyncSource();
+
+		Class entityType;
+		if (eventValue.isSelected()) {
+			if (taskValue.isSelected()) {
+				entityType = CalendarContent.class;
+			} else {
+				entityType = Event.class;
+			}
+		} else {
+			entityType = Task.class;
+		}
+		syncSource.setEntityType(entityType);
+	}
+
+	/**
+	 * Returns the panel name
+	 * 
+	 * @return the panel name
+	 */
+	protected String getPanelName() {
+		return PANEL_NAME;
+	}
+
+	/**
+	 * Returns the available types
+	 * 
+	 * @return the available types;
+	 */
+	protected List getTypes() {
+		List supportedTypes = new ArrayList();
+		supportedTypes.add(TYPE_LABEL_SIFE);
+		supportedTypes.add(TYPE_LABEL_SIFT);
+		supportedTypes.add(TYPE_LABEL_VCAL);
+		supportedTypes.add(TYPE_LABEL_ICAL);
+		return supportedTypes;
+	}
+
+	/**
+	 * Returns the type to select based on the given syncsource
+	 * 
+	 * @return the type to select based on the given syncsource
+	 */
+	protected String getTypeToSelect(SyncSource syncSource) {
+		String preferredType = null;
+		if (syncSource.getInfo() != null
+				&& syncSource.getInfo().getPreferredType() != null) {
+
+			preferredType = syncSource.getInfo().getPreferredType().getType();
+			if (TYPE_ICAL.equals(preferredType)) {
+				return TYPE_LABEL_ICAL;
+			}
+			if (TYPE_VCAL.equals(preferredType)) {
+				return TYPE_LABEL_VCAL;
+			}
+			if (TYPE_SIFE.equals(preferredType)) {
+				return TYPE_LABEL_SIFE;
+			}
+			if (TYPE_SIFT.equals(preferredType)) {
+				return TYPE_LABEL_SIFT;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Sets the source info of the given syncsource based on the given
+	 * selectedType
+	 * 
+	 * @param syncSource
+	 *            the source
+	 * @param selectedType
+	 *            the selected type
+	 */
+	public void setSyncSourceInfo(SyncSource syncSource, String selectedType) {
+		PIMSyncSource pimSource = (PIMSyncSource) syncSource;
+		ContentType[] contentTypes = null;
+		if (TYPE_LABEL_ICAL.equals(selectedType)) {
+			contentTypes = new ContentType[2];
+			contentTypes[0] = new ContentType(TYPE_ICAL, VERSION_ICAL);
+			contentTypes[1] = new ContentType(TYPE_VCAL, VERSION_VCAL);
+		} else if (TYPE_LABEL_VCAL.equals(selectedType)) {
+			contentTypes = new ContentType[2];
+			contentTypes[0] = new ContentType(TYPE_VCAL, VERSION_VCAL);
+			contentTypes[1] = new ContentType(TYPE_ICAL, VERSION_ICAL);
+		} else if (TYPE_LABEL_SIFE.equals(selectedType)) {
+			contentTypes = new ContentType[1];
+			contentTypes[0] = new ContentType(TYPE_SIFE, VERSION_SIFE);
+		} else if (TYPE_LABEL_SIFT.equals(selectedType)) {
+			contentTypes = new ContentType[1];
+			contentTypes[0] = new ContentType(TYPE_SIFT, VERSION_SIFT);
+		}
+
+		pimSource.setInfo(new SyncSourceInfo(contentTypes, 0));
+	}
+
+	// --------------------------------------------------------- Private methods
+
+	private boolean areEventsAllowed() {
+		if (typeCombo.getSelectedItem() == null) {
+			return false;
+		}
+
+		if (TYPE_LABEL_SIFT.equals((String) typeCombo.getSelectedItem())) {
+			return false;
+		}
+		return true;
+	}
+
+	private boolean areTasksAllowed() {
+		if (typeCombo.getSelectedItem() == null) {
+			return false;
+		}
+
+		if (TYPE_LABEL_SIFE.equals((String) typeCombo.getSelectedItem())) {
+			return false;
+		}
+		return true;
+	}
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/admin/PIMContactSyncSourceConfigPanel.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/admin/PIMContactSyncSourceConfigPanel.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/admin/PIMContactSyncSourceConfigPanel.java	(revision 2082)
@@ -0,0 +1,143 @@
+/**
+ * This class does something.
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.admin;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import br.com.prognus.psync.engine.source.PIMContactSyncSource;
+import br.com.prognus.psync.engine.source.PIMSyncSource;
+
+import com.funambol.admin.AdminException;
+import com.funambol.framework.engine.source.ContentType;
+import com.funambol.framework.engine.source.SyncSource;
+import com.funambol.framework.engine.source.SyncSourceInfo;
+
+public class PIMContactSyncSourceConfigPanel extends PIMSyncSourceConfigPanel
+		implements Serializable {
+
+	// --------------------------------------------------------------- Constants
+	protected static final String PANEL_NAME = "Edit PIM Contact SyncSource";
+
+	protected static final String TYPE_LABEL_SIFC = "SIF-C";
+
+	protected static final String TYPE_LABEL_VCARD = "VCard";
+
+	protected static final String TYPE_SIFC = "text/x-s4j-sifc";
+
+	protected static final String TYPE_VCARD = "text/x-vcard";
+
+	protected static final String VERSION_SIFC = "1.0";
+
+	protected static final String VERSION_VCARD = "2.1";
+
+	// ------------------------------------------------------------ Constructors
+
+	/** Creates a new instance of PIMContactSyncSourceConfigPanel. */
+	public PIMContactSyncSourceConfigPanel() {
+		super();
+	}
+
+	// ---------------------------------------------------------- Public methods
+
+	/**
+	 * Updates the form
+	 */
+	@Override
+	public void updateForm() {
+
+		if (!(getSyncSource() instanceof PIMContactSyncSource)) {
+			notifyError(new AdminException(
+					"This is not a PIMContactSyncSources! "
+							+ "Unable to process SyncSource values."));
+			return;
+		}
+
+		super.updateForm();
+	}
+
+	// ------------------------------------------------------- Protected methods
+
+	/**
+	 * Returns the panel name
+	 * 
+	 * @return the panel name
+	 */
+	@Override
+	protected String getPanelName() {
+		return PANEL_NAME;
+	}
+
+	/**
+	 * Returns the available types
+	 * 
+	 * @return the available types;
+	 */
+	@Override
+	protected List getTypes() {
+		List supportedTypes = new ArrayList();
+		supportedTypes.add(TYPE_LABEL_SIFC);
+		supportedTypes.add(TYPE_LABEL_VCARD);
+		return supportedTypes;
+	}
+
+	/**
+	 * Returns the type to select based on the given syncsource
+	 * 
+	 * @return the type to select based on the given syncsource
+	 */
+	@Override
+	protected String getTypeToSelect(SyncSource syncSource) {
+		String preferredType = null;
+		if (syncSource.getInfo() != null
+				&& syncSource.getInfo().getPreferredType() != null) {
+
+			preferredType = syncSource.getInfo().getPreferredType().getType();
+			if (TYPE_VCARD.equals(preferredType)) {
+				return TYPE_LABEL_VCARD;
+			}
+			if (TYPE_SIFC.equals(preferredType)) {
+				return TYPE_LABEL_SIFC;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Sets the source info of the given syncsource based on the given
+	 * selectedType
+	 */
+	@Override
+	public void setSyncSourceInfo(SyncSource syncSource, String selectedType) {
+		PIMSyncSource pimSource = (PIMSyncSource) syncSource;
+		ContentType[] contentTypes = null;
+		if (TYPE_LABEL_SIFC.equals(selectedType)) {
+			contentTypes = new ContentType[1];
+			contentTypes[0] = new ContentType(TYPE_SIFC, VERSION_SIFC);
+		} else if (TYPE_LABEL_VCARD.equals(selectedType)) {
+			contentTypes = new ContentType[1];
+			contentTypes[0] = new ContentType(TYPE_VCARD, VERSION_VCARD);
+		}
+
+		pimSource.setInfo(new SyncSourceInfo(contentTypes, 0));
+	}
+	// --------------------------------------------------------- Private methods
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/admin/PIMSyncSourceConfigPanel.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/admin/PIMSyncSourceConfigPanel.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/admin/PIMSyncSourceConfigPanel.java	(revision 2082)
@@ -0,0 +1,441 @@
+/**
+ * This class implements the configuration panel for a PIMSyncSource.
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.admin;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.border.TitledBorder;
+
+import org.apache.commons.lang.StringUtils;
+
+import br.com.prognus.psync.engine.source.PIMCalendarSyncSource;
+import br.com.prognus.psync.engine.source.PIMContactSyncSource;
+import br.com.prognus.psync.engine.source.PIMSyncSource;
+
+import com.funambol.admin.AdminException;
+import com.funambol.admin.mo.SyncSourceManagementObject;
+import com.funambol.admin.ui.SourceManagementPanel;
+import com.funambol.framework.engine.source.SyncSource;
+
+public abstract class PIMSyncSourceConfigPanel extends SourceManagementPanel
+		implements Serializable {
+
+	// --------------------------------------------------------------- Constants
+
+	protected static final String PANEL_NAME = "Edit PIM SyncSource";
+
+	public static final String NAME_ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_.";
+
+	// All possible PIM SyncSource's
+	public static final Class[] SYNCSOURCE = { PIMContactSyncSource.class,
+			PIMCalendarSyncSource.class, };
+
+	// ------------------------------------------------------------ Private data
+
+	/** Label for the panel's name */
+	protected JLabel panelName = new JLabel();
+
+	/** Border to pose the panel's title in evidence */
+	protected TitledBorder titledBorder;
+
+	protected JLabel nameLabel = new JLabel();
+
+	protected JTextField nameValue = new JTextField();
+
+	protected JLabel typeLabel = new JLabel();
+
+	protected JComboBox typeCombo = new JComboBox();
+
+	protected JLabel sourceUriLabel = new JLabel();
+
+	protected JTextField sourceUriValue = new JTextField();
+
+	protected JCheckBox encryption = new JCheckBox();
+
+	protected JCheckBox encoding = new JCheckBox();
+
+	protected JButton confirmButton = new JButton();
+
+	// ------------------------------------------------------------ Constructors
+
+	/**
+	 * Creates a new instance of PIMSyncSourceConfigPanel.
+	 */
+	public PIMSyncSourceConfigPanel() {
+		init();
+	}
+
+	/**
+	 * Updates the panel form with values from the linked SyncSource.
+	 */
+	public void updateForm() {
+
+		if (!(getSyncSource() instanceof PIMSyncSource)) {
+			notifyError(new AdminException("This is not a PIMSyncSource! "
+					+ "Unable to process SyncSource values."));
+			return;
+		}
+
+		PIMSyncSource syncSource = (PIMSyncSource) getSyncSource();
+
+		if (getState() == STATE_INSERT) {
+			confirmButton.setText("Add");
+		} else if (getState() == STATE_UPDATE) {
+			confirmButton.setText("Save");
+		}
+
+		sourceUriValue.setText(syncSource.getSourceURI());
+		nameValue.setText(syncSource.getName());
+
+		if (syncSource.getSourceURI() != null) {
+			sourceUriValue.setEditable(false);
+		}
+
+		// Preparing to populate the combo box...
+		typeCombo.removeAllItems();
+		List types = getTypes();
+		if (types == null) {
+			types = new ArrayList();
+		}
+		for (int i = 0; i < types.size(); i++) {
+			typeCombo.addItem(types.get(i));
+		}
+		String typeToSelect = getTypeToSelect(syncSource);
+		if (typeToSelect != null) {
+			typeCombo.setSelectedItem(typeToSelect);
+		} else {
+			typeCombo.setSelectedIndex(0);
+		}
+
+		SyncSourceManagementObject mo = (SyncSourceManagementObject) getManagementObject();
+		String transformationsRequired = mo.getTransformationsRequired();
+
+		if (transformationsRequired == null) {
+			encryption.setSelected(false);
+			encoding.setSelected(false);
+		} else {
+			if ("des;b64".equals(transformationsRequired)) {
+				encryption.setSelected(true);
+				encoding.setSelected(true);
+			} else if ("b64".equals(transformationsRequired)) {
+				encryption.setSelected(false);
+				encoding.setSelected(true);
+			} else {
+				encryption.setSelected(false);
+				encoding.setSelected(false);
+			}
+		}
+
+		// In any case, force the encoding for SIF data
+		if (isSIFSelected()) {
+			encoding.setSelected(true);
+			encoding.setEnabled(false);
+		}
+
+	}
+
+	/**
+	 * Set preferredSize of the panel
+	 * 
+	 * @return preferredSize of the panel
+	 */
+	public Dimension getPreferredSize() {
+		return new Dimension(525, 280);
+	}
+
+	// --------------------------------------------------------- Private methods
+
+	/**
+	 * Checks if the values provided by the user are all valid. If they are, the
+	 * method ends regularly.
+	 * 
+	 * @throws IllegalArgumentException
+	 *             if name, uri, type or directory are empty (null or
+	 *             zero-length)
+	 */
+	protected void validateValues() throws IllegalArgumentException {
+		String value = null;
+
+		value = nameValue.getText();
+		if (StringUtils.isEmpty(value)) {
+			throw new IllegalArgumentException("Field 'Name' cannot be empty. "
+					+ "Please provide a SyncSource name.");
+		}
+
+		if (!StringUtils.containsOnly(value, NAME_ALLOWED_CHARS.toCharArray())) {
+			throw new IllegalArgumentException(
+					"Only the following characters are allowed for field 'Name':"
+							+ "\n" + NAME_ALLOWED_CHARS);
+		}
+
+		value = (String) typeCombo.getSelectedItem();
+		if (StringUtils.isEmpty(value)) {
+			throw new IllegalArgumentException("Field 'Type' cannot be empty. "
+					+ "Please provide a SyncSource type.");
+		}
+
+		value = sourceUriValue.getText();
+		if (StringUtils.isEmpty(value)) {
+			throw new IllegalArgumentException(
+					"Field 'Source URI' cannot be empty. "
+							+ "Please provide a SyncSource URI.");
+		}
+	}
+
+	/**
+	 * Sets the SyncSource's properties with the values provided by the user.
+	 */
+	protected void getValues() {
+		PIMSyncSource syncSource = (PIMSyncSource) getSyncSource();
+
+		syncSource.setSourceURI(sourceUriValue.getText().trim());
+		syncSource.setName(nameValue.getText().trim());
+
+		String transformationsRequired = null;
+		if (encryption.isSelected()) {
+			transformationsRequired = "des;b64";
+		} else if (encoding.isSelected()) {
+			transformationsRequired = "b64";
+		}
+		SyncSourceManagementObject mo = (SyncSourceManagementObject) getManagementObject();
+		mo.setTransformationsRequired(transformationsRequired);
+
+		setSyncSourceInfo(syncSource, (String) typeCombo.getSelectedItem());
+	}
+
+	/**
+	 * Checks whether a SIF content type is selected.
+	 * 
+	 * @return true if in the combo box the selected string starts with "SIF"
+	 */
+	protected boolean isSIFSelected() {
+
+		if (typeCombo.getItemCount() == 0) {
+			return false;
+		}
+		return ((String) typeCombo.getSelectedItem()).startsWith("SIF");
+	}
+
+	/**
+	 * Adds extra components just above the confirm button.
+	 * 
+	 * @param x
+	 *            horizontal position
+	 * @param y
+	 *            vertical position
+	 * @param xGap
+	 *            standard horizontal gap
+	 * @param yGap
+	 *            standard vertical gap
+	 * @return the new vertical position
+	 */
+	protected int addExtraComponents(int x, int y, int xGap, int yGap) {
+		// Nothing
+
+		return y;
+	}
+
+	/**
+	 * Returns the panel name
+	 * 
+	 * @return the panel name
+	 */
+	protected String getPanelName() {
+		return PANEL_NAME;
+	}
+
+	/**
+	 * Returns the available types
+	 * 
+	 * @return the available types;
+	 */
+	protected abstract List getTypes();
+
+	/**
+	 * Returns the type to select based on the given syncsource
+	 * 
+	 * @return the type to select based on the given syncsource
+	 */
+	protected abstract String getTypeToSelect(SyncSource syncSource);
+
+	/**
+	 * Sets the source info of the given syncsource based on the given
+	 * selectedType
+	 * 
+	 * @param syncSource
+	 *            the source
+	 * @param selectedType
+	 *            the selected type
+	 */
+	protected abstract void setSyncSourceInfo(SyncSource syncSource,
+			String selectedType);
+
+	// --------------------------------------------------------- Private methods
+
+	/**
+	 * Creates the panel's components and layout.
+	 * 
+	 * @todo adjust layout
+	 */
+	private void init() {
+		// set layout
+		this.setLayout(null);
+		// set properties of label, position and border
+		// referred to the title of the panel
+		titledBorder = new TitledBorder("");
+		panelName.setFont(titlePanelFont);
+		panelName.setText(getPanelName());
+		panelName.setBounds(new Rectangle(14, 5, 316, 28));
+		panelName.setAlignmentX(SwingConstants.CENTER);
+		panelName.setBorder(titledBorder);
+
+		final int LABEL_X = 14;
+		final int VALUE_X = 170;
+		int y = 60;
+		final int GAP_X = 150;
+		final int GAP_Y = 30;
+
+		sourceUriLabel.setText("Source URI: ");
+		sourceUriLabel.setFont(defaultFont);
+		sourceUriLabel.setBounds(new Rectangle(LABEL_X, y, 150, 18));
+		sourceUriValue.setFont(defaultFont);
+		sourceUriValue.setBounds(new Rectangle(VALUE_X, y, 350, 18));
+
+		y += GAP_Y; // New line
+
+		nameLabel.setText("Name: ");
+		nameLabel.setFont(defaultFont);
+		nameLabel.setBounds(new Rectangle(LABEL_X, y, 150, 18));
+		nameValue.setFont(defaultFont);
+		nameValue.setBounds(new Rectangle(VALUE_X, y, 350, 18));
+		y += GAP_Y; // New line
+
+		typeLabel.setText("Type: ");
+		typeLabel.setFont(defaultFont);
+		typeLabel.setBounds(new Rectangle(LABEL_X, y, 150, 18));
+		typeCombo.setFont(defaultFont);
+		typeCombo.setBounds(new Rectangle(VALUE_X, y, 350, 18));
+
+		// What happens when the Type value is changed?
+		typeCombo.addItemListener(new ItemListener() {
+			public void itemStateChanged(ItemEvent e) {
+				if (isSIFSelected()) {
+					encoding.setSelected(true); // SIFs always encoded
+					encoding.setEnabled(false);
+				} else {
+					encryption.setSelected(false);
+					encoding.setSelected(false);
+					encoding.setEnabled(true);
+				}
+			}
+		});
+
+		y += GAP_Y; // New line
+		int x = LABEL_X;
+
+		y = addExtraComponents(x, y, GAP_X, GAP_Y); // Add other components, if
+		// needed
+
+		encryption.setText("Encrypt data");
+		encryption.setFont(defaultFont);
+		encryption.setSelected(false);
+		encryption.setBounds(x, y, 150, 25);
+
+		// What happens if the encryption is enabled?
+		encryption.addItemListener(new ItemListener() {
+			public void itemStateChanged(ItemEvent e) {
+				if (e.getStateChange() == e.SELECTED) {
+					encoding.setSelected(true); // Encryption implies encoding
+					encoding.setEnabled(false);
+				}
+				if (e.getStateChange() == e.DESELECTED) {
+					if (!isSIFSelected()) {
+						encoding.setEnabled(true);
+					}
+				}
+			}
+		});
+
+		y += GAP_Y; // New line
+
+		encoding.setText("Encode data in Base 64");
+		encoding.setFont(defaultFont);
+		encoding.setSelected(false);
+		encoding.setBounds(x, y, 150, 25);
+
+		y += GAP_Y; // New line
+		y += GAP_Y; // New line
+
+		confirmButton.setFont(defaultFont);
+		confirmButton.setText("Add");
+		confirmButton.setBounds(VALUE_X, y, 70, 25);
+
+		// What happens when the confirmButton is pressed?
+		confirmButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent event) {
+				try {
+					validateValues();
+					getValues();
+					if (getState() == STATE_INSERT) {
+						PIMSyncSourceConfigPanel.this
+								.actionPerformed(new ActionEvent(
+										PIMSyncSourceConfigPanel.this,
+										ACTION_EVENT_INSERT, event
+												.getActionCommand()));
+					} else {
+						PIMSyncSourceConfigPanel.this
+								.actionPerformed(new ActionEvent(
+										PIMSyncSourceConfigPanel.this,
+										ACTION_EVENT_UPDATE, event
+												.getActionCommand()));
+					}
+				} catch (Exception e) {
+					notifyError(new AdminException(e.getMessage(), e));
+				}
+			}
+		});
+
+		// Adds all components to the panel
+		this.add(panelName, null);
+		this.add(nameLabel, null);
+		this.add(sourceUriLabel, null);
+		this.add(sourceUriValue, null);
+		this.add(nameValue, null);
+		this.add(typeLabel, null);
+		this.add(typeCombo, null);
+		this.add(encryption, null);
+		this.add(encoding, null);
+		this.add(confirmButton, null);
+	}
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MySyncSourceAdminPanel.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MySyncSourceAdminPanel.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/MySyncSourceAdminPanel.java	(revision 2082)
@@ -0,0 +1,400 @@
+/*
+ * Funambol is a mobile platform developed by Funambol, Inc.
+ * Copyright (C) 2008 Funambol, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Affero General Public License version 3 as published by
+ * the Free Software Foundation with the addition of the following permission
+ * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
+ * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
+ * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses or write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA.
+ *
+ * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
+ * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU Affero General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU Affero General Public License
+ * version 3, these Appropriate Legal Notices must retain the display of the
+ * "Powered by Funambol" logo. If the display of the logo is not reasonably
+ * feasible for technical reasons, the Appropriate Legal Notices must display
+ * the words "Powered by Funambol".
+ */
+
+package br.com.prognus.psync;
+
+import java.io.Serializable;
+
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.border.TitledBorder;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.funambol.framework.engine.source.ContentType;
+import com.funambol.framework.engine.source.SyncSourceInfo;
+
+import com.funambol.admin.AdminException;
+import com.funambol.admin.ui.SourceManagementPanel;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ * This class implements the configuration panel for MySyncSource
+ *
+ */
+public class MySyncSourceAdminPanel
+extends SourceManagementPanel
+implements Serializable {
+
+    // --------------------------------------------------------------- Constants
+
+    /**
+     * Allowed characters for name and uri
+     */
+    public static final String NAME_ALLOWED_CHARS
+    = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_.";
+
+    // ------------------------------------------------------------ Private data
+    /** label for the panel's name */
+    private JLabel panelName = new JLabel();
+
+    /** border to evidence the title of the panel */
+    private TitledBorder  titledBorder1;
+
+    private JLabel           nameLabel          = new JLabel()     ;
+    private JTextField       nameValue          = new JTextField() ;
+    private JLabel           typeLabel          = new JLabel()     ;
+    private JTextField       typeValue          = new JTextField() ;
+    private JLabel           versionLabel       = new JLabel()     ;
+    private JTextField       versionValue       = new JTextField() ;
+
+    private JLabel           sourceUriLabel     = new JLabel()     ;
+    private JTextField       sourceUriValue     = new JTextField() ;
+    
+    private JLabel           myStringLabel      = new JLabel()     ;
+    private JTextField       myStringValue      = new JTextField() ;
+    
+    private JLabel           myIntLabel         = new JLabel()     ;
+    private JTextField       myIntValue         = new JTextField() ;
+    
+    private JLabel           myMapLabel         = new JLabel()     ;
+    private JTextField       myMapEntry         = new JTextField() ;
+    private JTextField       myMapValue         = new JTextField() ;
+
+    private JButton          confirmButton      = new JButton()    ;
+
+    private MySyncSource     syncSource         = null             ;
+
+    // ------------------------------------------------------------ Constructors
+
+    /**
+     * Creates a new MySyncSourceAdminPanel instance
+     */
+    public MySyncSourceAdminPanel() {
+        init();
+    }
+
+    // ----------------------------------------------------------- Private methods
+
+    /**
+     * Create the panel
+     * @throws Exception if error occures during creation of the panel
+     */
+    private void init(){
+        // set layout
+        this.setLayout(null);
+
+        // set properties of label, position and border
+        //  referred to the title of the panel
+        titledBorder1 = new TitledBorder("");
+
+        panelName.setFont(titlePanelFont);
+        panelName.setText("Edit My SyncSource");
+        panelName.setBounds(new Rectangle(14, 5, 316, 28));
+        panelName.setAlignmentX(SwingConstants.CENTER);
+        panelName.setBorder(titledBorder1);
+
+        int y  = 60;
+        int dy = 30;
+
+        sourceUriLabel.setText("Source URI: ");
+        sourceUriLabel.setFont(defaultFont);
+        sourceUriLabel.setBounds(new Rectangle(14, y, 150, 18));
+        sourceUriValue.setFont(defaultFont);
+        sourceUriValue.setBounds(new Rectangle(170, y, 350, 18));
+
+        y += dy;
+
+        nameLabel.setText("Name: ");
+        nameLabel.setFont(defaultFont);
+        nameLabel.setBounds(new Rectangle(14, y, 150, 18));
+        nameValue.setFont(defaultFont);
+        nameValue.setBounds(new Rectangle(170, y, 350, 18));
+
+        y += dy;
+
+        typeLabel.setText("Supported type: ");
+        typeLabel.setFont(defaultFont);
+        typeLabel.setBounds(new Rectangle(14, y, 150, 18));
+        typeValue.setFont(defaultFont);
+        typeValue.setBounds(new Rectangle(170, y, 350, 18));
+
+        y += dy;
+
+        versionLabel.setText("Supported version: ");
+        versionLabel.setFont(defaultFont);
+        versionLabel.setBounds(new Rectangle(14, y, 150, 18));
+        versionValue.setFont(defaultFont);
+        versionValue.setBounds(new Rectangle(170, y, 350, 18));
+        
+        y += dy;
+
+        myStringLabel.setText("MyString: ");
+        myStringLabel.setFont(defaultFont);
+        myStringLabel.setBounds(new Rectangle(14, y, 150, 18));
+        myStringValue.setFont(defaultFont);
+        myStringValue.setBounds(new Rectangle(170, y, 350, 18));
+        
+        y += dy;
+
+        myIntLabel.setText("MyInt: ");
+        myIntLabel.setFont(defaultFont);
+        myIntLabel.setBounds(new Rectangle(14, y, 150, 18));
+        myIntValue.setFont(defaultFont);
+        myIntValue.setBounds(new Rectangle(170, y, 35, 18));
+        
+        y += dy;
+
+        myMapLabel.setText("MyMap entry: ");
+        myMapLabel.setFont(defaultFont);
+        myMapLabel.setBounds(new Rectangle(14, y, 150, 18));
+        myMapEntry.setFont(defaultFont);
+        myMapEntry.setBounds(new Rectangle(170, y, 35, 18));
+        myMapValue.setFont(defaultFont);
+        myMapValue.setBounds(new Rectangle(210, y, 35, 18));
+
+        y += dy;
+        y += dy;
+
+        confirmButton.setFont(defaultFont);
+        confirmButton.setText("Add");
+        confirmButton.setBounds(170, y, 70, 25);
+
+        confirmButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent event ) {
+                try {
+                    validateValues();
+                    getValues();
+                    if (getState() == STATE_INSERT) {
+                        MySyncSourceAdminPanel.this.actionPerformed(
+                            new ActionEvent(MySyncSourceAdminPanel.this,
+                                            ACTION_EVENT_INSERT,
+                                            event.getActionCommand()));
+                    } else {
+                        MySyncSourceAdminPanel.this.actionPerformed(
+                            new ActionEvent(MySyncSourceAdminPanel.this,
+                                            ACTION_EVENT_UPDATE,
+                                            event.getActionCommand()));
+                    }
+                } catch (Exception e) {
+                    notifyError(new AdminException(e.getMessage()));
+                }
+            }
+        });
+
+        // add all components to the panel
+        this.add(panelName        , null);
+        this.add(nameLabel        , null);
+        this.add(nameValue        , null);
+        this.add(typeLabel        , null);
+        this.add(typeValue        , null);
+        this.add(versionLabel     , null);
+        this.add(versionValue     , null);
+        this.add(sourceUriLabel   , null);
+        this.add(sourceUriValue   , null);
+        this.add(myStringLabel    , null);
+        this.add(myStringValue    , null);
+        this.add(myIntLabel       , null);
+        this.add(myIntValue       , null);
+        this.add(myMapLabel       , null);
+        this.add(myMapEntry       , null);
+        this.add(myMapValue       , null);
+        this.add(confirmButton    , null);
+
+    }
+
+    /**
+     * Loads the given syncSource showing the name, uri and type in the panel's
+     * fields.
+     *
+     * @param syncSource the SyncSource instance
+     */
+    public void updateForm() {
+         if (!(getSyncSource() instanceof MySyncSource)) {
+          notifyError(
+              new AdminException(
+                  "This is not an MySyncSource! Unable to process SyncSource values."
+              )
+          );
+          return;
+        }
+        if (getState() == STATE_INSERT) {
+          confirmButton.setText("Add");
+        } else if (getState() == STATE_UPDATE) {
+          confirmButton.setText("Save");
+        }
+
+        this.syncSource = (MySyncSource) getSyncSource();
+
+        sourceUriValue.setText(syncSource.getSourceURI() );
+        nameValue.setText     (syncSource.getName()      );
+
+        SyncSourceInfo info = syncSource.getInfo();        
+        if (info != null) {
+            ContentType[] types = info.getSupportedTypes();
+
+            StringBuffer typesList = new StringBuffer(), versionsList = new StringBuffer();
+            for (int i=0; ((types != null) && (i<types.length)); ++i) {
+                typesList.append(types[i].getType());
+                versionsList.append(types[i].getVersion());
+                //
+                // Also if the SyncSourceInfo contains more contentTypes, we handle
+                // just the first one
+                //
+                break;
+            }
+
+            typeValue.setText(typesList.toString());
+            versionValue.setText(versionsList.toString());
+        }
+
+        if (this.syncSource.getSourceURI() != null) {
+            sourceUriValue.setEditable(false);
+        }
+        
+        if (syncSource.getMyString() != null) {
+            myStringValue.setText(syncSource.getMyString());
+        } else {
+            myStringValue.setText("");
+        }
+        myIntValue.setText(String.valueOf(syncSource.getMyInt()));
+        
+        Map map = syncSource.getMyMap();
+        if (map != null) {
+            Iterator i = map.keySet().iterator(); 
+            if (i.hasNext()) {
+                String key = (String)i.next();
+                myMapEntry.setText(key);
+                Object value = map.get(key);
+                myMapValue.setText(((value != null) ? (String)value : ""));
+            }
+        }
+    }
+
+    // ----------------------------------------------------------- Private methods
+    /**
+     * Checks if the values provided by the user are all valid. In caso of errors,
+     * a IllegalArgumentException is thrown.
+     *
+     * @throws IllegalArgumentException if:
+     *         <ul>
+     *         <li>name, uri, type or directory are empty (null or zero-length)
+     *         <li>the types list length does not match the versions list length
+     *         </ul>
+     */
+    private void validateValues() throws IllegalArgumentException {
+        String value = null;
+
+        value = nameValue.getText().trim();
+        if (StringUtils.isEmpty(value)) {
+            throw new
+            IllegalArgumentException(
+            "Field 'Name' cannot be empty. Please provide a SyncSource name.");
+        }
+
+        if (!StringUtils.containsOnly(value, NAME_ALLOWED_CHARS.toCharArray())) {
+            throw new
+            IllegalArgumentException(
+            "Only the following characters are allowed for field 'Name': \n" + NAME_ALLOWED_CHARS);
+        }
+
+        value = typeValue.getText().trim();
+        if (StringUtils.isEmpty(value)) {
+            throw new
+            IllegalArgumentException(
+            "Field 'Supported Type' cannot be empty. Please provide a type.");
+        }
+
+        value = versionValue.getText().trim();
+        if (StringUtils.isEmpty(value)) {
+            throw new
+            IllegalArgumentException(
+            "Field 'Supported Version' cannot be empty. Please provide a version.");
+        }
+
+        value = sourceUriValue.getText().trim();
+        if (StringUtils.isEmpty(value)) {
+            throw new
+            IllegalArgumentException(
+            "Field 'Source URI' cannot be empty. Please provide a SyncSource URI.");
+        }
+        
+        value = myIntValue.getText().trim();
+        try {
+            Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            throw new
+            IllegalArgumentException(
+            "Field 'myInt' must be an integer number.");
+        }
+        
+        value = myMapValue.getText().trim();
+        if (!StringUtils.isEmpty(value) && StringUtils.isEmpty(myMapEntry.getText())) {
+            throw new
+            IllegalArgumentException(
+            "Missing key for 'myMap'.");
+        }
+    }
+
+    /**
+     * Set syncSource properties with the values provided by the user.
+     */
+    private void getValues() {
+        syncSource.setSourceURI(sourceUriValue.getText().trim());
+        syncSource.setName     (nameValue.getText().trim()     );
+
+        ContentType[] contentTypes = new ContentType[] {
+            new ContentType(typeValue.getText(), versionValue.getText())
+        };
+
+        syncSource.setInfo(new SyncSourceInfo(contentTypes, 0));
+        
+        syncSource.setMyString(myStringValue.getText().trim());
+        syncSource.setMyInt(Integer.parseInt(myIntValue.getText().trim()));
+        
+        HashMap map = new HashMap();
+        map.put(myMapEntry.getText().trim(), myMapValue.getText().trim());
+        syncSource.setMyMap(map);
+    }
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/phones/s55/SyncRequestStore.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/phones/s55/SyncRequestStore.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/phones/s55/SyncRequestStore.java	(revision 2082)
@@ -0,0 +1,94 @@
+/**
+ * Copyright (C) 2003-2007 Funambol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Honest Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Honest Public License for more details.
+ *
+ * You should have received a copy of the Honest Public License
+ * along with this program; if not, write to Funambol,
+ * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
+ */
+package br.com.prognus.psync.phones.s55;
+
+import java.util.ArrayList;
+
+import com.funambol.framework.core.*;
+import com.funambol.framework.engine.pipeline.InputMessageProcessor;
+import com.funambol.framework.engine.pipeline.MessageProcessingContext;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+import com.funambol.framework.protocol.ProtocolUtil;
+
+/**
+ * This class store the synchronization alert code into processingContext.
+ *
+ * @version $Id: SyncRequestStore.java,v 1.3 2007-01-11 19:20:19 nichele Exp $
+ */
+public class SyncRequestStore implements InputMessageProcessor {
+
+    // ------------------------------------------------------------ Private data
+    private static final FunambolLogger log =
+            FunambolLoggerFactory.getLogger("engine.pipeline");
+
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * Process input message and set MessageProcessingContext property.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     * @throws Sync4jException
+     */
+    public void preProcessMessage(MessageProcessingContext processingContext,
+                                  SyncML message) throws Sync4jException {
+        if (log.isTraceEnabled()) {
+            log.trace("preProcessMessage: SyncRequestStore of input message");
+        }
+
+        Put clientCapabilities = null;
+
+        try {
+            AbstractCommand[] allCmd =
+            (AbstractCommand[])message.getSyncBody().getCommands().toArray(
+                                                        new AbstractCommand[0]);
+            //
+            //Extract only alert commands
+            //
+            for (int i=0; (allCmd != null) && (i<allCmd.length); i++) {
+                if (Alert.COMMAND_NAME.equals(allCmd[i].getName())) {
+                    Alert alertCmd = (Alert)allCmd[i];
+                    int code = alertCmd.getData();
+                    processingContext.setRequestProperty(SlowFastSyncManagement.PROPERTY_CODE, String.valueOf(code));
+                }//end if cmds
+            }//end allCmd
+
+            //
+            //Extract the type of device
+            //
+            ArrayList clientCapabilitiesList = ProtocolUtil.filterCommands(allCmd, Put.class);
+
+            if (clientCapabilitiesList.size()>0) {
+                clientCapabilities = (Put)clientCapabilitiesList.get(0);
+
+                if (log.isTraceEnabled()) {
+                    log.trace("preProcessMessage: name clientCapabilities "
+                                                + clientCapabilities.getName());
+                }
+
+                DevInfItem item = (DevInfItem)clientCapabilities.getItems().get(0);
+                DevInf di  = item.getDevInfData().getDevInf();
+                String man = di.getMan();
+                processingContext.setRequestProperty(SlowFastSyncManagement.PROPERTY_MANTYPE, man);
+            }
+        } catch(Exception e) {
+            log.error("Errore preprocessing the request", e);
+        }
+    }
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/phones/s55/SlowFastSyncManagement.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/phones/s55/SlowFastSyncManagement.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/phones/s55/SlowFastSyncManagement.java	(revision 2082)
@@ -0,0 +1,97 @@
+/**
+ * Copyright (C) 2003-2007 Funambol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Honest Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Honest Public License for more details.
+ *
+ * You should have received a copy of the Honest Public License
+ * along with this program; if not, write to Funambol,
+ * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
+ */
+package br.com.prognus.psync.phones.s55;
+
+import com.funambol.framework.core.*;
+import com.funambol.framework.engine.pipeline.MessageProcessingContext;
+import com.funambol.framework.engine.pipeline.OutputMessageProcessor;
+import com.funambol.framework.logging.FunambolLogger;
+import com.funambol.framework.logging.FunambolLoggerFactory;
+
+/**
+ * This class check if the request of slow or fast sync is correct.
+ *
+ * @version $Id: SlowFastSyncManagement.java,v 1.3 2007-01-11 19:20:19 nichele Exp $
+ */
+public class SlowFastSyncManagement implements OutputMessageProcessor {
+
+    // --------------------------------------------------------------- Constants
+
+    public static final String PROPERTY_MANTYPE =
+        "funambol.foundation.slowfastsyncmanagement.MANTYPE";
+    public static final String PROPERTY_CODE    =
+        "funambol.foundation.slowfastsyncmanagement.CODE"   ;
+
+    // ------------------------------------------------------------ Private data
+    private static final FunambolLogger log =
+            FunambolLoggerFactory.getLogger("engine.pipeline");
+
+    private static final String PHONE_SIEMENS = "siemens";
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * Process and manipulate the output message.
+     *
+     * @param processingContext the message processing context
+     * @param message the message to be processed
+     * @throws Sync4jException
+     */
+    public void postProcessMessage(MessageProcessingContext processingContext,
+                                   SyncML message) throws Sync4jException {
+        if (log.isTraceEnabled()) {
+            log.trace("postProcessMessage: SlowFastSyncManagement of output message");
+        }
+
+        try {
+
+            //
+            //Only if the device is Siemens55 apply the code
+            //
+            String man = (String)processingContext.getRequestProperty(PROPERTY_MANTYPE);
+            if (log.isTraceEnabled()) {
+                log.trace("postProcessMessage: man: " + man);
+            }
+
+            if (man != null && man.toLowerCase().indexOf(PHONE_SIEMENS) != -1) {
+
+                AbstractCommand[] allCmd = (AbstractCommand[])
+                    message.getSyncBody().getCommands().toArray(
+                                                        new AbstractCommand[0]);
+                //
+                //Extract only alert commands
+                //
+                for (int i=0; (allCmd != null) && (i<allCmd.length); i++) {
+                    if (Alert.COMMAND_NAME.equals(allCmd[i].getName())) {
+                        Alert alertCmd = (Alert)allCmd[i];
+                        int code = alertCmd.getData();
+
+                        int codeRequest = Integer.parseInt("" + processingContext.getRequestProperty(PROPERTY_CODE));
+
+                        if (code != codeRequest) {
+                            if (code == AlertCode.SLOW) {
+                                alertCmd.setData(AlertCode.TWO_WAY);
+                            }
+                        }
+                    }//end if cmds
+                }//end allCmd
+            }
+
+        } catch(Exception e) {
+            log.error("Errore postprocessing the request", e);
+        }
+    }
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/util/Def.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/util/Def.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/util/Def.java	(revision 2082)
@@ -0,0 +1,35 @@
+/**
+ *
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.util;
+
+import com.funambol.framework.engine.SyncItemState;
+
+public class Def {
+
+	public static final String LOGGER_NAME = "PSYNC";
+
+	public static final char PIM_STATE_NEW = SyncItemState.NEW;
+
+	public static final char PIM_STATE_UPDATED = SyncItemState.UPDATED;
+
+	public static final char PIM_STATE_DELETED = SyncItemState.DELETED;
+
+	public static final char PIM_STATE_UNCHANGED = SyncItemState.UNKNOWN;
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/exception/EntityException.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/exception/EntityException.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/exception/EntityException.java	(revision 2082)
@@ -0,0 +1,66 @@
+/**
+ *
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.exception;
+
+public class EntityException extends Exception {
+
+	/**
+	 * Creates a new instance of <code>EntityException</code> without detail
+	 * message.
+	 */
+	public EntityException() {
+
+	}
+
+	/**
+	 * Constructs an instance of <code>EntityException</code> with the
+	 * specified detail message.
+	 * 
+	 * @param msg
+	 *            the detail message.
+	 */
+	public EntityException(String msg) {
+		super(msg);
+	}
+
+	/**
+	 * Constructs an instance of <code>EntityException</code> with the
+	 * specified exception.
+	 * 
+	 * @param cause
+	 *            Throwable
+	 */
+	public EntityException(Throwable cause) {
+		super(cause);
+	}
+
+	/**
+	 * Constructs an instance of <code>EntityException</code> with the
+	 * specified detail message.
+	 * 
+	 * @param msg
+	 *            String
+	 * @param cause
+	 *            Throwable
+	 */
+	public EntityException(String msg, Throwable cause) {
+		super(msg, cause);
+	}
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/exception/PIMDBAccessException.java
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/exception/PIMDBAccessException.java	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/exception/PIMDBAccessException.java	(revision 2082)
@@ -0,0 +1,66 @@
+/**
+ *
+ * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
+ * @copyright Copyright 2007-2008 Prognus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package br.com.prognus.psync.exception;
+
+public class PIMDBAccessException extends Exception {
+
+	/**
+	 * Creates a new instance of <code>EntityException</code> without detail
+	 * message.
+	 */
+	public PIMDBAccessException() {
+
+	}
+
+	/**
+	 * Constructs an instance of <code>EntityException</code> with the
+	 * specified detail message.
+	 * 
+	 * @param msg
+	 *            the detail message.
+	 */
+	public PIMDBAccessException(String msg) {
+		super(msg);
+	}
+
+	/**
+	 * Constructs an instance of <code>EntityException</code> with the
+	 * specified exception.
+	 * 
+	 * @param cause
+	 *            Throwable
+	 */
+	public PIMDBAccessException(Throwable cause) {
+		super(cause);
+	}
+
+	/**
+	 * Constructs an instance of <code>EntityException</code> with the
+	 * specified detail message.
+	 * 
+	 * @param msg
+	 *            String
+	 * @param cause
+	 *            Throwable
+	 */
+	public PIMDBAccessException(String msg, Throwable cause) {
+		super(msg, cause);
+	}
+
+}
Index: contrib/funambol/trunk/modules/psync/src/main/external/readme.txt
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/external/readme.txt	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/external/readme.txt	(revision 2082)
@@ -0,0 +1,2 @@
+This directory contains file that a module needs at installation time.
+These files will not be included in the final Funambol archive.
Index: contrib/funambol/trunk/modules/psync/src/main/config/psync/calendar/VEventSource.xml
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/config/psync/calendar/VEventSource.xml	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/config/psync/calendar/VEventSource.xml	(revision 2082)
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<java version="1.4.0" class="java.beans.XMLDecoder">
+ <object class="br.com.prognus.psync.engine.source.PIMCalendarSyncSource">
+ <void property="name">
+   <string>calendario</string>
+ </void>
+ <void property="sourceURI">
+   <string>calendario</string>
+ </void>
+ <void property="entityType">
+   <class>com.funambol.common.pim.calendar.Event</class>
+ </void>
+ <void property="info">
+   <object class="com.funambol.framework.engine.source.SyncSourceInfo">
+     <void property="supportedTypes">
+     <array class="com.funambol.framework.engine.source.ContentType" length="2">
+      <void index="0">
+       <object class="com.funambol.framework.engine.source.ContentType">
+        <void property="type">
+         <string>text/x-vcalendar</string>
+        </void>
+        <void property="version">
+         <string>1.0</string>
+        </void>
+       </object>
+      </void>
+      <void index="1">
+       <object class="com.funambol.framework.engine.source.ContentType">
+        <void property="type">
+         <string>text/calendar</string>
+        </void>
+        <void property="version">
+         <string>2.0</string>
+        </void>
+       </object>
+      </void>
+     </array>
+     </void>  <!-- supportedTypes -->
+     <void property="preferred">
+       <int>0</int>
+     </void>
+   </object>
+ </void> <!-- info -->
+ </object>
+</java>
Index: contrib/funambol/trunk/modules/psync/src/main/config/psync/contact/VCardSource.xml
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/config/psync/contact/VCardSource.xml	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/config/psync/contact/VCardSource.xml	(revision 2082)
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<java version="1.4.0" class="java.beans.XMLDecoder">
+ <object class="br.com.prognus.psync.engine.source.PIMContactSyncSource">
+ <void property="name">
+   <string>catalogo</string>
+ </void>
+ <void property="sourceURI">
+   <string>catalogo</string>
+ </void>
+ <void property="info">
+   <object class="com.funambol.framework.engine.source.SyncSourceInfo">
+     <void property="supportedTypes">
+       <array class="com.funambol.framework.engine.source.ContentType" length="1">
+         <void index="0">
+           <object class="com.funambol.framework.engine.source.ContentType">
+             <void property="type">
+               <string>text/x-vcard</string>
+             </void>
+             <void property="version">
+               <string>2.1</string>
+             </void>
+           </object>
+         </void>
+       </array>
+     </void>  <!-- supportedTypes -->
+     <void property="preferred">
+       <int>0</int>
+     </void>
+   </object>
+ </void> <!-- info -->
+ </object>
+</java>
Index: contrib/funambol/trunk/modules/psync/src/main/install/install.xml
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/install/install.xml	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/install/install.xml	(revision 2082)
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Funambol is a mobile platform developed by Funambol, Inc.
+ * Copyright (C) 2008 Funambol, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Affero General Public License version 3 as published by
+ * the Free Software Foundation with the addition of the following permission
+ * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
+ * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
+ * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses or write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA.
+ *
+ * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
+ * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU Affero General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU Affero General Public License
+ * version 3, these Appropriate Legal Notices must retain the display of the
+ * "Powered by Funambol" logo. If the display of the logo is not reasonably
+ * feasible for technical reasons, the Appropriate Legal Notices must display
+ * the words "Powered by Funambol".
+ */
+-->
+
+<!-- 
+    Change the information below as appropriate
+-->
+<project name="psync Module Installation" default="install" basedir=".">
+    <!-- =================================================================== -->
+    <!-- install - custom module installation                                -->
+    <!-- =================================================================== -->
+    <target name="install">
+        <echo message="psync installation"/>
+        
+        <!--
+             Put here your installation code...
+        -->
+
+        <echo message="psync installation successfully completed"/>
+    </target>
+</project>
Index: contrib/funambol/trunk/modules/psync/src/main/lixo-bean/psync/calendar/VEventSource.xml
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/lixo-bean/psync/calendar/VEventSource.xml	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/lixo-bean/psync/calendar/VEventSource.xml	(revision 2082)
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<java version="1.4.0" class="java.beans.XMLDecoder">
+ <object class="br.com.prognus.psync.engine.source.PIMCalendarSyncSource">
+ <void property="name">
+   <string>calendario</string>
+ </void>
+ <void property="sourceURI">
+   <string>calendario</string>
+ </void>
+ <void property="entityType">
+   <class>com.funambol.common.pim.calendar.Event</class>
+ </void>
+ <void property="info">
+   <object class="com.funambol.framework.engine.source.SyncSourceInfo">
+     <void property="supportedTypes">
+     <array class="com.funambol.framework.engine.source.ContentType" length="2">
+      <void index="0">
+       <object class="com.funambol.framework.engine.source.ContentType">
+        <void property="type">
+         <string>text/x-vcalendar</string>
+        </void>
+        <void property="version">
+         <string>1.0</string>
+        </void>
+       </object>
+      </void>
+      <void index="1">
+       <object class="com.funambol.framework.engine.source.ContentType">
+        <void property="type">
+         <string>text/calendar</string>
+        </void>
+        <void property="version">
+         <string>2.0</string>
+        </void>
+       </object>
+      </void>
+     </array>
+     </void>  <!-- supportedTypes -->
+     <void property="preferred">
+       <int>0</int>
+     </void>
+   </object>
+ </void> <!-- info -->
+ </object>
+</java>
Index: contrib/funambol/trunk/modules/psync/src/main/lixo-bean/psync/contact/VCardSource.xml
===================================================================
--- contrib/funambol/trunk/modules/psync/src/main/lixo-bean/psync/contact/VCardSource.xml	(revision 2082)
+++ contrib/funambol/trunk/modules/psync/src/main/lixo-bean/psync/contact/VCardSource.xml	(revision 2082)
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<java version="1.4.0" class="java.beans.XMLDecoder">
+ <object class="br.com.prognus.psync.engine.source.PIMContactSyncSource">
+ <void property="name">
+   <string>catalogo</string>
+ </void>
+ <void property="sourceURI">
+   <string>catalogo</string>
+ </void>
+ <void property="info">
+   <object class="com.funambol.framework.engine.source.SyncSourceInfo">
+     <void property="supportedTypes">
+       <array class="com.funambol.framework.engine.source.ContentType" length="1">
+         <void index="0">
+           <object class="com.funambol.framework.engine.source.ContentType">
+             <void property="type">
+               <string>text/x-vcard</string>
+             </void>
+             <void property="version">
+               <string>2.1</string>
+             </void>
+           </object>
+         </void>
+       </array>
+     </void>  <!-- supportedTypes -->
+     <void property="preferred">
+       <int>0</int>
+     </void>
+   </object>
+ </void> <!-- info -->
+ </object>
+</java>
