src/mgdschema.c

00001 /* Midgard schema , records and objects definition.
00002    
00003   Copyright (C) 2004,2005 Piotr Pokora <pp@infoglob.pl>
00004   
00005   This program is free software; you can redistribute it and/or modify it
00006   under the terms of the GNU Lesser General Public License as published
00007   by the Free Software Foundation; either version 2 of the License, or
00008   (at your option) any later version.
00009 
00010   This program is distributed in the hope that it will be useful,
00011   but WITHOUT ANY WARRANTY; without even the implied warranty of
00012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013   GNU General Public License for more details.
00014 
00015   You should have received a copy of the GNU General Public License
00016   along with this program; if not, write to the Free Software
00017   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00018 */
00019 
00020 #include <config.h>
00021 #include "midgard/midgard_schema.h"
00022 #include "midgard/types.h"
00023 #include "midgard/midgard_legacy.h"
00024 #include <libxml/parser.h>
00025 #include <libxml/tree.h>
00026 #include "midgard/midgard_datatypes.h"
00027 #include "midgard/midgard_type.h"
00028 #include "schema.h"
00029 
00030 /* TODO tune header files , no need to include string.h while we need to include midgard.h in almost every case */
00031 
00032 /* Global schema_trash used when MgdSchema is destroyed 
00033  * Trash must be global as we share pointers between different schemas.
00034  * Especially Midgard core object's MgdSchemaTypes are shared.
00035  * */
00036 static GSList *schema_trash = NULL;
00037 
00038 /* Global schema file path, it's a pointer to filename being parsed.
00039  * Only for warning purposes */
00040 static const gchar *parsed_schema = NULL;
00041 
00042 /* prototypes */ 
00043 void _copy_schemas(gpointer key, gpointer value, gpointer userdata);
00044 
00045 static MgdSchemaPropertyAttr *_mgd_schema_property_attr_new(){
00046         
00047         MgdSchemaPropertyAttr *prop = g_new(MgdSchemaPropertyAttr, 1);
00048         prop->gtype = G_TYPE_NONE;
00049         prop->type = NULL;
00050         prop->dbtype = NULL;
00051         prop->field = NULL;
00052         prop->table = NULL;
00053         prop->upfield = NULL;
00054         prop->parentfield = NULL;
00055         prop->primaryfield = NULL;
00056         prop->is_multilang = FALSE;
00057         prop->link = NULL;
00058         prop->link_target = NULL;
00059         prop->is_primary = FALSE;
00060         prop->is_reversed = FALSE;
00061         prop->is_link = FALSE;
00062         
00063         return prop; 
00064 }
00065 
00066 static void _mgd_schema_property_attr_free(MgdSchemaPropertyAttr *prop)
00067 {
00068         g_assert(prop != NULL);
00069 
00070         g_free((gchar *)prop->type);
00071         g_free((gchar *)prop->dbtype);
00072         g_free((gchar *)prop->field);
00073         g_free((gchar *)prop->table);
00074         g_free((gchar *)prop->upfield);
00075         g_free((gchar *)prop->parentfield);
00076         g_free((gchar *)prop->primaryfield);
00077         g_free((gchar *)prop->link);
00078         g_free((gchar *)prop->link_target);
00079 
00080         g_free(prop);
00081 }
00082 
00083 void _destroy_property_hash(gpointer key, gpointer value, gpointer userdata)
00084 {
00085         MgdSchemaPropertyAttr *prop = (MgdSchemaPropertyAttr *) value;
00086         gchar *name = (gchar *) key;
00087         
00088         if(prop)
00089                 _mgd_schema_property_attr_free(prop);
00090 
00091         if(name)
00092                 g_free(name);
00093 }
00094 
00095 static MgdSchemaTypeQuery *_mgd_schema_type_query_new()
00096 {
00097         MgdSchemaTypeQuery *query = g_new(MgdSchemaTypeQuery, 1);
00098         query->select = NULL;
00099         query->select_full = NULL;
00100         query->from = NULL;
00101         query->where = NULL;
00102         query->table = NULL;
00103         query->link = NULL;
00104         query->tables = g_strdup("");
00105         query->primary = NULL;
00106         query->parentfield = NULL;
00107         query->upfield = NULL;
00108         query->use_lang = FALSE;
00109 
00110         return query;
00111 }
00112 
00113 static void _mgd_schema_type_query_free(MgdSchemaTypeQuery *query)
00114 {
00115         g_assert(query != NULL);
00116         
00117         g_free((gchar *)query->select);
00118         g_free((gchar *)query->select_full);
00119         /* pointer to type->table, g_free((gchar *)query->from); */
00120         g_free((gchar *)query->where);
00121         g_free((gchar *)query->table);
00122         g_free((gchar *)query->primary);
00123         g_free((gchar *)query->link);
00124         g_free((gchar *)query->tables);
00125         g_free((gchar *)query->parentfield);
00126         g_free((gchar *)query->upfield);
00127         
00128 
00129         g_free(query);
00130 }
00131 
00132 void _destroy_query_hash(gpointer key, gpointer value, gpointer userdata)
00133 {
00134         MgdSchemaTypeQuery *query = (MgdSchemaTypeQuery *) value;
00135         gchar *name = (gchar *) key;
00136         
00137         if(query)
00138                 _mgd_schema_type_query_free(query);
00139 
00140         if(name)
00141                 g_free(name);
00142 }
00143 
00144 static MgdSchemaTypeAttr *_mgd_schema_type_attr_new()
00145 {
00146         MgdSchemaTypeAttr *type = g_new(MgdSchemaTypeAttr, 1);
00147         type->base_index = 0;
00148         type->num_properties = 0;
00149         type->params = NULL;
00150         type->properties = NULL;
00151         type->table = NULL;
00152         type->parent = NULL;
00153         type->primary = NULL;
00154         type->property_up = NULL;
00155         type->property_parent = NULL;                           
00156         type->property_count = 0;
00157         type->use_lang = FALSE;
00158         type->tables = g_hash_table_new(g_str_hash, g_str_equal);
00159         type->prophash = g_hash_table_new(g_str_hash, g_str_equal);
00160         type->query = _mgd_schema_type_query_new();
00161         type->childs = NULL;
00162         
00163         return type;
00164 }
00165 
00166 static void _mgd_schema_type_attr_free(MgdSchemaTypeAttr *type)
00167 {
00168         g_assert(type != NULL);
00169 
00170         g_free((gchar *)type->table);
00171         g_free((gchar *)type->parent);
00172         g_free((gchar *)type->primary);
00173         g_free((gchar *)type->property_up);
00174         g_free((gchar *)type->property_parent);
00175 
00176         g_hash_table_foreach(type->tables, _destroy_query_hash, NULL);
00177         g_hash_table_destroy(type->tables);     
00178         
00179         g_hash_table_foreach(type->prophash, _destroy_property_hash, NULL);
00180         g_hash_table_destroy(type->prophash);
00181 
00182         _mgd_schema_type_query_free(type->query);
00183 
00184         g_free(type->params);
00185         g_free(type->properties);
00186         
00187         g_free(type);
00188 }
00189 
00190 /* return type's struct  or NULL if type is not in schema */
00191 MgdSchemaTypeAttr *
00192 midgard_schema_lookup_type (MidgardSchema *schema, gchar *name)
00193 {
00194         g_assert(schema != NULL);
00195         g_assert(name != NULL);
00196 
00197         return (MgdSchemaTypeAttr *) g_hash_table_lookup(schema->types, name);  
00198 }
00199 
00200 
00212 GHashTable *
00213 midgard_schema_lookup_type_member(MidgardSchema *schema, const gchar *typename, gchar *property)
00214 {
00215         g_assert(schema != NULL);
00216         g_assert(typename != NULL);
00217         g_assert(property != NULL);
00218         
00219         MgdSchemaTypeAttr *sts;
00220         
00221         sts = midgard_schema_lookup_type(schema, (gchar *)typename);
00222         
00223         if (sts != NULL) 
00224                 return g_hash_table_lookup(sts->prophash, property);
00225 
00226         return NULL;
00227 }
00228 
00229 /* Define which element names should be addedd to schema. 
00230  * We change them only here , and in xml file itself.
00231  */
00232 static const gchar *mgd_complextype[] = { "type", "property", "Schema", "description", "include", NULL };
00233 static const gchar *mgd_attribute[] = { "name", "table", "parent", "parentfield", 
00234         "type", "link", "upfield", "field", "multilang", "reverse", "primaryfield", "dbtype", NULL };
00235 
00236 /* RESERVED type names */
00237 static const gchar *rtypes[] = { 
00238         "midgard_sitegroup", 
00239         "midgard_account",
00240         NULL
00241 };
00242 
00243 /* RESERVED table names */
00244 static const gchar *rtables[] = {
00245         "sitegroup",
00246         "repligard",
00247         "midgard_account",
00248         NULL
00249 };
00250 
00251 /* RESERVED column names */
00252 static const gchar *rcolumns[] = {
00253         "metadata_",
00254         "sitegroup",
00255         "group", /* This is MySQL reserved word */
00256         NULL
00257 };
00258 
00259 static gboolean strv_contains(const char **strv, const xmlChar *str) {
00260         g_assert(strv != NULL);
00261         g_assert(str != NULL);
00262         while (*strv != NULL) {
00263                 if (g_str_equal(*strv++, str)) {
00264                         return TRUE;
00265                 }
00266         }
00267         return FALSE;
00268 }
00269 
00270 static void check_metadata_column(const xmlChar *column){
00271         
00272         if(g_str_has_prefix((const gchar *)column, "metadata_"))
00273                 g_critical("Column names prefixed with 'metadata_' are reserved ones");
00274 }
00275 
00276 static void check_property_name(const xmlChar *name, const gchar *typename){
00277         
00278         g_assert(name != NULL);
00279 
00280         if(g_strrstr_len((const gchar *)name, strlen((const gchar *)name), "_") != NULL)
00281                 g_critical("Type '%s', property '%s'. Underscore not allowed in property names!",
00282                                 typename, name);
00283 
00284         /* temporary solution for midgard_quota */
00285         if(!g_str_equal(typename, "midgard_quota"))
00286                 if(g_str_equal(name, "sitegroup"))
00287                         g_critical("Type '%s'.Property name 'sitegroup' is reserved!",
00288                                         typename);
00289 
00290         if(g_str_equal(name, "guid"))
00291                 g_critical("Type '%s'.Property name 'guid' is reserved!",
00292                                 typename);
00293                 
00294 }
00295 
00296 
00297 /* Get all type attributes */
00298 static void
00299 _get_type_attributes(xmlNode * node, MgdSchemaTypeAttr *type_attr)
00300 {
00301         xmlAttr *attr;
00302         xmlChar *attrval;
00303 
00304         if (node != NULL){
00305 
00306                 attr = node->properties;
00307                 attr = attr->next; /* avoid getting already added */
00308 
00309                 while (attr != NULL){
00310 
00311                         if(!strv_contains(mgd_attribute, attr->name)){
00312                                 g_warning("Wrong attribute '%s' in '%s' on line %ld",
00313                                                 attr->name, parsed_schema, xmlGetLineNo(node)); 
00314                         }       
00315                         attrval = xmlNodeListGetString (node->doc, attr->children, 1);
00316                                         
00317                         if(g_str_equal(attr->name, "table")) {
00318                                 /* Check if table name is reserved one */
00319                                 if (strv_contains(rtables, attrval)) {
00320                                         g_critical("'%s' is reserved table name",
00321                                                         attrval);
00322                                 }                                                               
00323                                 type_attr->table = g_strdup((const gchar *)attrval);
00324                         }
00325 
00326                         if(g_str_equal(attr->name, "parent"))
00327                                 type_attr->parent = g_strdup((gchar *)attrval);
00328         
00329                         xmlFree(attrval);
00330                         attr = attr->next;                      
00331                 }
00332         }
00333 }
00334 
00335 /* Get property attributes */
00336 static void 
00337 _get_property_attributes(xmlNode * node, 
00338                 MgdSchemaTypeAttr *type_attr, MgdSchemaPropertyAttr *prop_attr)
00339 {
00340         xmlAttr *attr;
00341         xmlChar *attrval;
00342 
00343         if (node != NULL){
00344 
00345                 attr = node->properties;
00346                 attr = attr->next;
00347 
00348                 while (attr != NULL){
00349 
00350                         attrval = xmlNodeListGetString (node->doc, attr->children, 1);
00351                 
00352                         if(!strv_contains(mgd_attribute, attr->name)){
00353                                 g_warning("Wrong attribute '%s' in '%s' on line %ld",
00354                                                 attr->name, parsed_schema, xmlGetLineNo(node));
00355                         }
00356 
00357                         if(g_str_equal(attr->name, "type")){
00358                                 prop_attr->type = g_strdup((const gchar*)attrval);
00359                         
00360                                 if(g_str_equal(attrval, "string"))
00361                                         prop_attr->gtype = MGD_TYPE_STRING;
00362 
00363                                 if(g_str_equal(attrval, "integer"))
00364                                         prop_attr->gtype = MGD_TYPE_INT;
00365 
00366                                 if(g_str_equal(attrval, "unsigned integer"))
00367                                         prop_attr->gtype = MGD_TYPE_UINT;
00368 
00369                                 if(g_str_equal(attrval, "float"))
00370                                         prop_attr->gtype = MGD_TYPE_FLOAT;
00371 
00372                                 if(g_str_equal(attrval, "boolean"))
00373                                         prop_attr->gtype = MGD_TYPE_BOOLEAN;
00374 
00375                                 if(g_str_equal(attrval, "bool"))
00376                                         prop_attr->gtype = MGD_TYPE_BOOLEAN;
00377                                 
00378                                 if(g_str_equal(attrval, "datetime"))
00379                                         prop_attr->gtype = MGD_TYPE_TIMESTAMP;
00380                                 
00381                                 if(g_str_equal(attrval, "longtext"))
00382                                         prop_attr->gtype = MGD_TYPE_LONGTEXT;
00383                                 
00384                                 if(g_str_equal(attrval, "text"))
00385                                         prop_attr->gtype = MGD_TYPE_LONGTEXT;                           
00386 
00387                                 if(g_str_equal(attrval, "guid"))
00388                                         prop_attr->gtype = MGD_TYPE_GUID;
00389                                 
00390                         }
00391                         
00392                         if(g_str_equal(attr->name, "dbtype"))
00393                                 prop_attr->dbtype = g_strdup((gchar *)attrval);
00394                         
00395                         if(g_str_equal(attr->name, "field")){
00396                                 /* Check if column name is reserved one */
00397                                 check_metadata_column(attrval);
00398                                 if (strv_contains(rcolumns, attrval)) {
00399                                         g_critical("'%s' is reserved column name",
00400                                                         attrval);
00401                                 }                                                               
00402                                 prop_attr->field = g_strdup((gchar *)attrval);
00403                         }
00404                         
00405                         if(g_str_equal(attr->name, "table")){
00406                                 /* Check if table name is reserved one */
00407                                 if (strv_contains(rtables, attrval)) {
00408                                         g_critical("'%s' is reserved table name",
00409                                                         attrval);
00410                                 }
00411                                 prop_attr->table = g_strdup((gchar *)attrval);
00412                         }
00413 
00414                         if(g_str_equal(attr->name, "upfield")){
00415                                 check_metadata_column(attrval);
00416                                 /* Check if column name is reserved one */
00417                                 if (strv_contains(rcolumns, attrval)) {
00418                                         g_critical("'%s' is reserved column name",
00419                                                         attrval);
00420                                 }                               
00421                                 if(!type_attr->property_up){
00422                                         xmlChar *tmpattr = 
00423                                                 xmlGetProp (node, (const xmlChar *)"name");
00424                                         type_attr->property_up = g_strdup((gchar *)tmpattr);
00425                                         type_attr->query->upfield = g_strdup((gchar *)attrval);
00426                                         xmlFree(tmpattr);
00427                                 } else {
00428                                         g_warning("upfield redefined!");
00429                                 }
00430                                 prop_attr->upfield = g_strdup((gchar *)attrval);                        
00431                         }
00432 
00433                         if(g_str_equal(attr->name, "parentfield")){
00434                                 check_metadata_column(attrval);
00435                                 /* Check if column name is reserved one */
00436                                 if (strv_contains(rcolumns, attrval)) {
00437                                         g_critical("'%s' is reserved column name",
00438                                                         attrval);
00439                                 }
00440                                 if(!type_attr->property_parent){
00441                                         xmlChar *tmpattr = xmlGetProp (node, (const xmlChar *)"name");
00442                                         type_attr->property_parent = g_strdup((gchar *)tmpattr);
00443                                         type_attr->query->parentfield = g_strdup((gchar *)attrval);
00444                                         xmlFree(tmpattr);
00445                                 } else {                         
00446                                         g_warning("parentfield redefined!");
00447                                 }
00448                                 prop_attr->parentfield = g_strdup((gchar *)attrval);
00449                         }                       
00450 
00451                         if(g_str_equal(attr->name, "multilang")) {
00452                                 if(g_str_equal(attrval, "yes")) {
00453                                         prop_attr->is_multilang = TRUE; 
00454                                         type_attr->use_lang = TRUE;
00455                                 }
00456                         }
00457 
00458                         if(g_str_equal(attr->name, "primaryfield")) {
00459                                 check_metadata_column(attrval);
00460                                 /* Check if column name is reserved one */
00461                                 if (strv_contains(rcolumns, attrval)) {
00462                                         g_critical("'%s' is reserved column name",
00463                                                         attrval);
00464                                 }
00465                                 prop_attr->primaryfield = g_strdup((gchar *)attrval);
00466                                 prop_attr->is_primary = TRUE;
00467                                 xmlChar *tmpattr = xmlGetProp (node, (const xmlChar *)"name");
00468                                 type_attr->primary = g_strdup((gchar *)tmpattr);
00469                                 type_attr->query->primary = g_strdup((gchar *)attrval);
00470                                 xmlFree(tmpattr);
00471                         }
00472 
00473                         if(g_str_equal(attr->name, "reverse")) {
00474                                 if(g_str_equal(attrval, "yes"))
00475                                         prop_attr->is_reversed = TRUE;  
00476                         }
00477                         
00478                         if(g_str_equal(attr->name, "link")) {
00479                                 gchar **link = g_strsplit((gchar *)attrval, ":", -1);
00480                                 prop_attr->link = g_strdup((gchar *)link[0]);
00481                                 prop_attr->is_link = TRUE;
00482                                 if(link[1])
00483                                         prop_attr->link_target = 
00484                                                 g_strdup((gchar *)link[1]);
00485                                 else 
00486                                         prop_attr->link_target =
00487                                                 g_strdup("guid");
00488 
00489                                 g_strfreev(link);
00490                         }
00491 
00492                         xmlFree(attrval);
00493                         attr = attr->next;                                                              
00494                 }
00495         }
00496 }
00497 
00498 /* Parse the whole file and make tree with nodes and properties.
00499  * Create huge hash of hashes, according to names which are defined
00500  * in ondef and attrdef chars. The only one hardcoded node's name is 
00501  * "name". I do not think it should force anyone to hardcode more things.
00502  * The rest is automagic :)
00503  */
00504 static void
00505 _get_element_names (xmlNode * curn , MgdSchemaTypeAttr *type_attr, MidgardSchema *schema)
00506 {
00507         xmlNode *obj = NULL;
00508         xmlChar *nv = NULL, *dparent;
00509         MgdSchemaTypeAttr *duplicate;
00510         MgdSchemaPropertyAttr *prop_attr;
00511         gchar *tmpstr;
00512 
00513         for (obj = curn; obj; obj = obj->next){
00514                 
00515                 if (obj->type == XML_ELEMENT_NODE){
00516                         /* Get nodes with "name" name. Check if obj->name was defined */
00517                         nv = xmlGetProp (obj, (const xmlChar *)"name"); 
00518                         
00519                         if(!strv_contains(mgd_complextype, obj->name)){
00520                                 g_warning("Wrong node name '%s' in '%s' on line %ld",
00521                                                 obj->name, parsed_schema, xmlGetLineNo(obj));
00522                         }
00523 
00524                         if(g_str_equal(obj->name, "type")) {
00525 
00526                                 /* Check if type name is reserved one */
00527                                 if (strv_contains(rtypes, nv)) {
00528                                         g_critical("'%s' is reserved type name",
00529                                                         nv);
00530 
00531                                 }
00532                                 
00533                                 if (g_str_equal(obj->name, "type")) {
00534                                         duplicate = midgard_schema_lookup_type(
00535                                                         schema, (gchar *)nv);
00536                                         
00537                                         if (duplicate != NULL) {
00538                                                 g_error("%s:%s already added to schema!",
00539                                                                 obj->name, nv);
00540                                         } else {
00541                                                 type_attr = _mgd_schema_type_attr_new();
00542                                                 g_hash_table_insert(schema->types,
00543                                                                 g_strdup((gchar *)nv), type_attr);
00544                                                 _get_type_attributes (obj, type_attr);
00545                                         }
00546                                 } 
00547                         }
00548 
00549                         if (g_str_equal(obj->name, "property")){
00550                                 
00551                                 dparent = xmlGetProp(obj->parent,
00552                                                 (const xmlChar *)"name");
00553                                 check_property_name(nv, (const gchar *)dparent);
00554                                 /* g_debug("\t Property: %s ", nv); */ 
00555                                 prop_attr = g_hash_table_lookup(
00556                                                 type_attr->prophash, (gchar *)nv);
00557                                 
00558                                 if (prop_attr != NULL) {
00559                                         g_warning("Property '%s' already added to %s",
00560                                                         nv ,dparent);
00561                                 } else {
00562                                         /* FIXME
00563                                          * one prop_attr seems to be assigned nowhere */
00564                                         prop_attr = _mgd_schema_property_attr_new();
00565                                         _get_property_attributes(obj, type_attr, prop_attr);
00566                                         if(prop_attr->is_primary 
00567                                                         && prop_attr->gtype != MGD_TYPE_UINT){
00568                                                 tmpstr = (gchar *)xmlGetProp(obj->parent,
00569                                                                 (const xmlChar *)"name");
00570                                                 g_message(" %s - type for primaryfield not defined"
00571                                                                 " as 'unsigned integer'."
00572                                                                 " Forcing uint type"
00573                                                                 , tmpstr);
00574                                                 g_free(tmpstr);
00575                                         }
00576                                         g_hash_table_insert(type_attr->prophash, 
00577                                                         g_strdup((gchar *)nv), prop_attr);
00578                                 }                                       
00579                                 xmlFree(dparent);                               
00580                         }
00581                         if(g_str_equal(obj->name, "description")){
00582 
00583                                 xmlChar *value = xmlGetProp(obj, (const xmlChar *)"value");
00584                                 xmlFree(value);                         
00585                         }
00586                         xmlFree(nv);
00587                         if (obj->children != NULL)
00588                                 _get_element_names (obj->children, type_attr, schema);   
00589                 }
00590         }
00591 }
00592 
00593 /* Start processing type's properties data. 
00594  * This function is called from __get_tdata_foreach 
00595  */
00596 void __get_pdata_foreach(gpointer key, gpointer value, gpointer user_data)
00597 {
00598         MgdSchemaTypeAttr *type_attr  = (MgdSchemaTypeAttr *) user_data;  
00599         MgdSchemaPropertyAttr *prop_attr = (MgdSchemaPropertyAttr *) value;
00600         const gchar *table, *tables, *primary, *parentname, *ext_table = NULL;
00601         gchar *tmp_sql = NULL , *nick = "";
00602         MgdSchemaTypeQuery *tquery;
00603         /* type_attr->num_properties is used ( and is mandatory! ) 
00604          * when we register new object types and set class properties */
00605         guint n = ++type_attr->num_properties;;
00606         const gchar *pname = (gchar *) key;
00607         const gchar *fname = NULL, *upfield = NULL, *parentfield = NULL; 
00608         gchar *tmpstr = NULL;
00609         GParamSpec **params = type_attr->params;
00610         GString *tmp_gstring_sql, *table_sql;
00611         
00612         /* Set default string type if not set */
00613         if(!prop_attr->type) {
00614                 prop_attr->type = g_strdup("string");
00615                 prop_attr->gtype = MGD_TYPE_STRING;
00616         }               
00617         
00618         table = prop_attr->table;
00619         tables = type_attr->query->tables;
00620         parentname = table;
00621 
00622         /* TODO
00623          * Set ACL data, create sql queries, add additional data
00624          * All data required is present at this moment.
00625          */
00626         
00627         /* Search for upfield and parentfield definition.
00628          * We will use them ( if found ) for nick ( table.field ) definition 
00629          * without need to define field attribute.  */
00630         
00631         upfield = prop_attr->upfield;
00632         parentfield = prop_attr->parentfield;
00633         primary = prop_attr->primaryfield;
00634         
00635         ext_table = table;
00636         
00637         /* "external" tables for type */
00638         
00639         /* Create GHashTable for all tables described in type's schema definition */
00640         if (ext_table != NULL){
00641                 
00642                 /* Check if table key already exists , and create one if not */
00643                 if ((tquery = g_hash_table_lookup(type_attr->tables, ext_table)) == NULL){
00644                         tquery = _mgd_schema_type_query_new();
00645                         g_hash_table_insert(type_attr->tables, g_strdup(ext_table), tquery);      
00646                 }
00647                 
00648                 if(g_strstr_len (tables, strlen(tables), ext_table) == NULL){
00649                         tmpstr = g_strconcat(tables, ext_table, ",", NULL);
00650                         g_free((gchar *)tables);
00651                         type_attr->query->tables = g_strdup(tmpstr);
00652                         g_free(tmpstr);
00653                 }
00654                 
00655                 table_sql = g_string_new("");
00656                 if(tquery->select)
00657                         g_string_append(table_sql, tquery->select);
00658                 
00659                 if (prop_attr->is_multilang)
00660                         tquery->use_lang = TRUE;
00661                 
00662                 tquery->from = (gchar *)ext_table;
00663                 fname = prop_attr->field;
00664         
00665                 if (fname != NULL) {
00666                         tmpstr = g_strjoin(".", ext_table, fname,NULL);
00667                 } else {
00668                         tmpstr = g_strjoin(".", ext_table, pname,NULL);
00669                 }
00670                 nick = g_strdup(tmpstr);
00671                 
00672                 tmp_gstring_sql = g_string_new(" ");            
00673                 if(prop_attr->gtype == MGD_TYPE_TIMESTAMP) {                    
00674                         g_string_append_printf(tmp_gstring_sql,
00675                                         "NULLIF(%s,'0000-00-00 00:00:00') AS %s",
00676                                         tmpstr, pname);
00677                 } else {
00678                         g_string_append_printf(tmp_gstring_sql,
00679                                         "%s AS %s",
00680                                         tmpstr, pname);
00681                 }
00682                 g_free(tmpstr);
00683                 tmpstr = g_string_free(tmp_gstring_sql, FALSE);
00684                 /* Avoid duplicated coma */
00685                 if(tquery->select)
00686                         g_string_append(table_sql, ", ");
00687                 g_string_append(table_sql, tmpstr);
00688                 g_free(tmpstr);
00689                 g_free(tquery->select);
00690                 tquery->select = g_string_free(table_sql, FALSE);
00691                 
00692                 g_hash_table_insert(type_attr->tables, (gchar *)ext_table, tquery);
00693                 /* "external" tables end */
00694         } 
00695         
00696         table = type_attr->table;
00697         
00698         if (ext_table == NULL) {
00699                 tmpstr = NULL;
00700                 type_attr->query->from = (gchar *)table;
00701                 fname = prop_attr->field;
00702                 if ( (fname == NULL ) && ((upfield == NULL) && (parentfield == NULL)) 
00703                                 && (primary == NULL)){
00704                         tmpstr = g_strjoin(".", table, pname,NULL);
00705                 } else if((fname != NULL) && ((parentfield == NULL) || (upfield == NULL))) {
00706                         tmpstr = g_strjoin(".", table, fname,NULL);
00707                 }
00708                 if(tmpstr){
00709                         table_sql = g_string_new("");
00710                         if(type_attr->query->select)
00711                                 g_string_append(table_sql, type_attr->query->select);                   
00712                         nick = g_strdup(tmpstr);
00713                         tmp_gstring_sql = g_string_new(" ");
00714                         if(prop_attr->gtype == MGD_TYPE_TIMESTAMP) {
00715                                 g_string_append_printf(tmp_gstring_sql,
00716                                                 "NULLIF(%s,'0000-00-00 00:00:00') AS %s",
00717                                                 tmpstr, pname);
00718                         } else {
00719                                 g_string_append_printf(tmp_gstring_sql,
00720                                                 "%s AS %s",
00721                                                 tmpstr, pname);
00722                         }
00723                         g_free(tmpstr);
00724                         tmpstr = g_string_free(tmp_gstring_sql, FALSE);
00725                         /* Do not prepend coma if type select is empty */
00726                         if(type_attr->query->select)
00727                                 g_string_append(table_sql, ",");
00728                         g_string_append(table_sql, tmpstr);     
00729                         g_free(tmpstr);
00730                         g_free(type_attr->query->select);
00731                         type_attr->query->select = g_string_free(table_sql, FALSE);
00732                 }
00733         }
00734         /* FIXME , this needs to be refactored for real property2field mapping */
00735         if(!type_attr->query->primary)
00736                 type_attr->query->primary =  g_strdup("guid");
00737 
00738         if(!type_attr->primary){
00739                 type_attr->primary = g_strdup("guid");
00740         }
00741         
00742 
00743         if(primary != NULL) {
00744                 nick = g_strjoin(".", table, primary, NULL);
00745                 tmpstr = g_strconcat(nick, " AS ", pname, NULL);
00746                 tmp_sql = g_strjoin(",", tmpstr, type_attr->query->select,NULL);
00747                 if(type_attr->query->select)
00748                         g_free(type_attr->query->select);
00749                 type_attr->query->select = g_strdup(tmp_sql);
00750                 g_free(tmpstr);
00751                 g_free(tmp_sql);                        
00752         }
00753 
00754         /* Set field which is used as up.
00755          * At the same time we define property_up which is keeps such data  */ 
00756         if(upfield != NULL) {
00757                 nick = g_strjoin(".", table, upfield, NULL);
00758                 tmpstr = g_strconcat(nick, " AS ", pname, NULL);
00759                 tmp_sql = g_strjoin(",", tmpstr, type_attr->query->select,NULL);
00760                 if(type_attr->query->select)
00761                         g_free(type_attr->query->select);
00762                 type_attr->query->select = g_strdup(tmp_sql);
00763                 g_free(tmpstr);
00764                 g_free(tmp_sql);
00765         }
00766         
00767         /* Set parentfield and property_parent */
00768         if(parentfield != NULL) {    
00769                 nick = g_strjoin(".", table, parentfield, NULL);
00770                 tmpstr = g_strconcat(nick, " AS ", pname, NULL);
00771                 tmp_sql = g_strjoin(",", tmpstr, type_attr->query->select,NULL);
00772                 if(type_attr->query->select)
00773                         g_free(type_attr->query->select);
00774                 type_attr->query->select = g_strdup(tmp_sql);
00775                 g_free(tmpstr);
00776                 g_free(tmp_sql);
00777         }
00778         
00779         /* Force G_TYPE_UINT type for primaryfield */
00780         if((prop_attr->is_primary) && (prop_attr->gtype != G_TYPE_UINT))
00781                 prop_attr->gtype = G_TYPE_UINT;
00782                         
00783         /* Create param_specs for object's properties */
00784         switch (prop_attr->gtype) {
00785                 
00786                 case MGD_TYPE_STRING:
00787                         params[n-1] = g_param_spec_string(
00788                                         pname, nick, "",
00789                                         "",  G_PARAM_READWRITE);
00790                         break;
00791 
00792                 case MGD_TYPE_UINT:
00793                         params[n-1] = g_param_spec_uint(
00794                                         pname, nick, "",
00795                                         0, G_MAXUINT32, 0, G_PARAM_READWRITE);
00796                         break;
00797 
00798                 case MGD_TYPE_INT:
00799                         params[n-1] = g_param_spec_int(
00800                                         pname, nick, "",
00801                                         G_MININT32, G_MAXINT32, 0, G_PARAM_READWRITE);
00802                         break;
00803 
00804                 case MGD_TYPE_FLOAT:
00805                         params[n-1] = g_param_spec_float(
00806                                         pname, nick, "",
00807                                         -G_MAXFLOAT, G_MAXFLOAT, 0, G_PARAM_READWRITE);
00808                         break;
00809 
00810                 case MGD_TYPE_BOOLEAN:
00811                         params[n-1] = g_param_spec_boolean(
00812                                         pname, nick, "",
00813                                         FALSE, G_PARAM_READWRITE);
00814                         break;
00815 
00816                 default:
00817                         params[n-1] = g_param_spec_string(
00818                                         pname, nick, "",
00819                                         "", G_PARAM_READWRITE);                 
00820                                         
00821         }
00822         g_free(nick);    
00823 }
00824 
00825 /* Append all tables' select to type's full_select */
00826 void _set_object_full_select(gpointer key, gpointer value, gpointer userdata)
00827 {
00828         GString *fullselect = (GString *) userdata;
00829         MgdSchemaTypeQuery *query = (MgdSchemaTypeQuery *) value;
00830         
00831         g_string_append_printf(fullselect, 
00832                         ",%s", query->select);
00833 }
00834 
00835 
00836 /* Get types' data. Type's name and its values.
00837  * We can not start creating GParamSpec while we parse xml file.
00838  * This is internally used with _register_types function. At this 
00839  * moment we can count all properties of type and call sizeof
00840  * with correct values
00841  */ 
00842 
00843 void __get_tdata_foreach(gpointer key, gpointer value, gpointer user_data)
00844 {
00845         GType new_type;
00846         MgdSchemaTypeAttr *type_attr = (MgdSchemaTypeAttr *) value;
00847         guint np;       
00848         gchar *tmpstr;
00849         
00850         if(g_type_from_name(key))
00851                 return;
00852         
00853         np = g_hash_table_size(type_attr->prophash);
00854 
00855         type_attr->params = g_malloc(sizeof(GParamSpec*)*(np+1)); 
00856 
00857         if (np > 0) {
00858                 g_hash_table_foreach(type_attr->prophash, __get_pdata_foreach, type_attr);
00859                 
00860                 /* add NULL as last value, 
00861                  * we will use ud.param as GParamSpec for object's properties. */ 
00862                 
00863                 type_attr->params[np] = NULL;
00864                 
00865                 /* Define tree management fields and tables */
00866 
00867                 if (type_attr->table != NULL ) {
00868                         tmpstr = g_strconcat(type_attr->query->tables, type_attr->table, NULL); 
00869                         g_free((gchar *)type_attr->query->tables);
00870                         type_attr->query->tables = g_strdup(tmpstr);
00871                         g_free(tmpstr);
00872                 }
00873 
00874                 //type_attr->parent = typename;
00875                 
00876                 /* Set NULL to child_cname, we will fill this value when all types
00877                  * are already registered ( _schema_postconfig ).  */ 
00878                 type_attr->childs = NULL;
00879                 
00880                 GString *fullselect = g_string_new("");
00881                 g_string_append(fullselect , type_attr->query->select );
00882                 g_hash_table_foreach(type_attr->tables,  _set_object_full_select, fullselect);
00883                 type_attr->query->select_full = g_string_free(fullselect, FALSE);
00884 
00885                 /* Register type , and initialize class. We can not add properties while 
00886                  * class is registered and we can not initialize class with properties later.
00887                  * Or rather "we should not" do this later. */ 
00888                 if (type_attr->params != NULL) {
00889 
00890                         new_type = midgard_type_register(key, type_attr);
00891                         if (new_type){
00892                                 GObject *foo = g_object_new(new_type, NULL);
00893                                 g_object_unref(foo); 
00894                         }
00895                 }
00896         
00897         } else {
00898                 g_warning("Type %s has less than 1 property!", (gchar *)key);
00899         }
00900 }
00901 
00902 /* Copy source schema types ( class names ) and its MgdSchemaType structures
00903  * to destination schema */
00904 void _copy_schemas(gpointer key, gpointer value, gpointer userdata)
00905 {
00906         gchar *typename = (gchar *)key;
00907         MgdSchemaTypeAttr *type = (MgdSchemaTypeAttr *)value;
00908         MgdSchema *ts = (MgdSchema *) userdata;
00909 
00910         if(!g_hash_table_lookup(ts->types, typename)) {
00911                 
00912                 if(type != NULL)
00913                         g_hash_table_insert(ts->types, g_strdup(typename), type); 
00914         }
00915 }
00916 
00917 /* We CAN NOT define some data during __get_tdata_foreach.
00918  * parent and childs relation ( for example ) must be done AFTER
00919  * all types are registered and all types internal structures are
00920  * already initialized and defined
00921  */ 
00922 void __postconfig_schema_foreach(gpointer key, gpointer value, gpointer user_data)
00923 {
00924         MgdSchemaTypeAttr *type_attr = (MgdSchemaTypeAttr *) value, *parenttype;
00925         MidgardSchema *schema = (MidgardSchema *) user_data;
00926         gchar *typename = (gchar *) key;
00927         const gchar *parentname;
00928 
00929         parentname = type_attr->parent;
00930         
00931         if (parentname  != NULL ){
00932 
00933                 /* Set child type name for parent's one */
00934                 /* PP: I use g_type_from_name(typename)) as gslist->data because
00935                  * typename ( even typecasted to char ) doesn't work.
00936                  * If you think this is wrong and have better solution , 
00937                  * feel free to change this code. */
00938                 
00939                 /* WARNING!! , all types which has parenttype defined will be appended to 
00940                  * this list. It doesn't matter if they are "registered" in such schema.
00941                  * Every GObjectClass has only one corresponding C structure , so it is impossible
00942                  * to initialize new MgdSchemaTypeAttr per every classname 
00943                  * in every schema and keep them  separated for every GObjectClass , 
00944                  * as we define only one GType.
00945                  * It may be resolved by expensive hash lookups for every new object instance.
00946                  */
00947 
00948                 if ((parenttype = midgard_schema_lookup_type(schema, (gchar *)parentname)) != NULL){
00949                         /* g_debug("type %s, parent %s", typename, parentname); */
00950                         if (!g_slist_find(parenttype->childs, 
00951                                                 (gpointer)g_type_from_name(typename))) {
00952                                 parenttype->childs = 
00953                                         g_slist_append(parenttype->childs, 
00954                                                         (gpointer)g_type_from_name(typename));
00955                         }                       
00956                 }
00957         }
00958 }
00959 
00964 static const char *NAMESPACE = "http://www.midgard-project.org/repligard/1.4";
00965 
00966 /* Forward declarations used by read_schema_path() */
00967 static void read_schema_directory(GHashTable *files, const char *directory);
00968 static void read_schema_file(GHashTable *files, const char *file);
00969 
00975 static void read_schema_path(GHashTable *files, const char *path) {
00976         g_assert(files != NULL);
00977         g_assert(path != NULL);
00978         if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
00979                 read_schema_directory(files, path);
00980         } else if (g_file_test(path, G_FILE_TEST_EXISTS)) {
00981                 read_schema_file(files, path);
00982         } else {
00983                 g_warning("Schema path %s not found", path);
00984         }
00985 }
00986 
00997 static void read_schema_file(GHashTable *files, const char *file) {
00998         g_assert(files != NULL);
00999         g_assert(file != NULL);
01000 
01001         /* Guard against duplicate loading of schema files */
01002         if (g_hash_table_lookup(files, file) != NULL) {
01003                 g_warning("Skipping already seen schema file %s", file);
01004                 return;
01005         }
01006 
01007         /* Parse this schema file */
01008         /* g_debug("Reading schema file %s", file); */
01009         xmlDocPtr doc = xmlParseFile(file);
01010         if (doc == NULL) {
01011                 g_warning("Skipping malformed schema file %s", file);
01012                 return;
01013         }
01014         xmlNodePtr root = xmlDocGetRootElement(doc);
01015         if (root == NULL
01016             || root->ns == NULL
01017             || !g_str_equal(root->ns->href, NAMESPACE)
01018             || !g_str_equal(root->name, "Schema")) {
01019                 g_warning("Skipping invalid schema file %s", file);
01020                 xmlFreeDoc(doc);
01021                 return;
01022         }
01023 
01024         /* Add the schema file to the hash table */
01025         g_hash_table_insert(files, g_strdup(file), doc);
01026 
01027         /* Read all included schema files */
01028         xmlNodePtr node = root->children;
01029         while (node != NULL) {
01030                 xmlChar *attr = xmlGetNoNsProp(node, (xmlChar *) "name");
01031                 if (node->type == XML_ELEMENT_NODE
01032                     && node->ns != NULL
01033                     && g_str_equal(node->ns->href, NAMESPACE)
01034                     && g_str_equal(node->name, "include")
01035                     && attr != NULL) {
01036                         GError *error = NULL;
01037                         gchar *name = g_filename_from_utf8(
01038                                 (const gchar *) attr, -1, NULL, NULL, &error);
01039                         if (name == NULL) {
01040                                 g_warning("g_filename_from_utf8: %s", error->message);
01041                                 g_error_free(error);
01042                         } else if  (g_path_is_absolute(name)) {
01043                                 read_schema_path(files, name);
01044                                 g_free(name);
01045                         } else {
01046                                 gchar *dir = g_path_get_dirname(file);
01047                                 gchar *path = g_build_filename(dir, name, NULL);
01048                                 read_schema_path(files, path);
01049                                 g_free(path);
01050                                 g_free(dir);
01051                                 g_free(name);
01052                         }
01053                 }
01054                 if (attr != NULL) {
01055                         xmlFree(attr);
01056                 }
01057                 node = node->next;
01058         }
01059         /* xmlFreeDoc(doc); */ /* wtf? */       
01060 }
01061 
01069 static void read_schema_directory(GHashTable *files, const char *directory) {
01070         g_assert(files != NULL);
01071         g_assert(directory != NULL);
01072 
01073         GError *error = NULL;
01074         GDir *dir = g_dir_open(directory, 0, &error);
01075         if (dir != NULL) {
01076                 const gchar *file = g_dir_read_name(dir);
01077                 while (file != NULL) {
01078                         gchar *path = g_build_filename(directory, file, NULL);
01079                         if (g_str_has_prefix(file, ".")) {
01080                                 /* skip hidden files and directories */
01081                         } else if (g_str_has_prefix(file, "#")) {
01082                                 /* skip backup files and directories */
01083                         } else if (!g_file_test(path, G_FILE_TEST_IS_DIR)) {
01084                                 /* recurse into subdirectories */
01085                                 read_schema_directory(files, path);
01086                         } else if (g_str_has_suffix(file, ".xml")) {
01087                                 /* read xml file, guaranteed to exist */
01088                                 read_schema_file(files, path);
01089                         }
01090                         g_free(path);
01091                         file = g_dir_read_name(dir);
01092                 }
01093                 g_dir_close(dir);
01094         } else {
01095                 g_warning("g_dir_open: %s", error->message);
01096                 g_error_free(error);
01097         }
01098 }
01099 
01109 static GHashTable *read_schema_files(const char *path) {
01110         g_assert(path != NULL);
01111         GHashTable *files = g_hash_table_new_full(
01112                 g_str_hash, g_str_equal, g_free, (GDestroyNotify) xmlFreeDoc);
01113         read_schema_path(files, path);
01114         return files;
01115 }
01116 
01117 static void parse_schema(gpointer key, gpointer value, gpointer user_data) {
01118         g_assert(key != NULL);
01119         g_assert(value != NULL);
01120         g_assert(user_data != NULL);
01121         parsed_schema = (const gchar *)key;
01122 
01123         MidgardSchema *schema = (MidgardSchema *) user_data;
01124         xmlDocPtr doc = (xmlDocPtr) value;
01125         xmlNodePtr root = xmlDocGetRootElement(doc);
01126         _get_element_names(root, NULL, schema);
01127 }
01128 
01129 /* API functions */
01130 
01131 /* Initializes basic Midgard classes */
01132 void midgard_schema_init(MidgardSchema *self){
01133         
01134         g_assert(self != NULL);
01135         midgard_schema_read_file(self, MIDGARD_GLOBAL_SCHEMA);
01136 }
01137 
01138 /* Checks if classname is registsred for schema. */
01139 gboolean midgard_schema_type_exists(MidgardSchema *self, const gchar *classname)
01140 {
01141         g_assert(self != NULL);
01142         g_assert(classname != NULL);
01143         
01144         if(g_hash_table_lookup(self->types, classname))
01145                 return TRUE;
01146 
01147         return FALSE;
01148 }
01149 
01150 void midgard_schema_read_file(
01151                 MidgardSchema *schema, const gchar *filename) 
01152 {
01153         g_assert(schema != NULL);
01154         g_assert(filename != NULL);
01155 
01156         GHashTable *files = read_schema_files(filename);
01157         g_hash_table_foreach(files, parse_schema, schema);
01158         g_hash_table_destroy(files);
01159 
01160         /* register types */
01161         g_hash_table_foreach(schema->types, __get_tdata_foreach, schema);
01162     
01163         /* Do postconfig for every initialized schema */
01164         g_hash_table_foreach(schema->types, __postconfig_schema_foreach, schema);
01165 }
01166 
01167 gboolean midgard_schema_read_dir(
01168                 MidgardSchema *gschema, const gchar *dirname)
01169 {
01170         const gchar *fname = NULL;
01171         gchar *fpfname = NULL ;
01172         GDir *dir;
01173         gint visible = 0;
01174 
01175         dir = g_dir_open(dirname, 0, NULL);
01176         
01177         if (dir != NULL) {
01178                 
01179                 while ((fname = g_dir_read_name(dir)) != NULL) {
01180 
01181                         visible = 1;
01182                         fpfname = g_strconcat(dirname, "/", fname, NULL);
01183                         
01184                         /* Get files only with xml extension, 
01185                          * swap files with '~' suffix will be ignored */
01186                         if (!g_file_test (fpfname, G_FILE_TEST_IS_DIR) 
01187                                         && g_str_has_suffix(fname, ".xml")){
01188                                 
01189                                 /* Hide hidden files */
01190                                 if(g_str_has_prefix(fname, "."))
01191                                         visible = 0;
01192                                 
01193                                 /* Hide recovery files if such exist */
01194                                 if(g_str_has_prefix(fname, "#"))
01195                                         visible = 0;
01196                                 
01197                                 if ( visible == 0)
01198                                         g_warning("File %s ignored!", fpfname);
01199                                 
01200                                 if(visible == 1){                                       
01201                                         /* FIXME, use fpath here */
01202                                         midgard_schema_read_file(gschema, fpfname);
01203                                 }
01204                         }
01205                         g_free(fpfname);
01206                         
01207                         /* Do not free ( or change ) fname. 
01208                          * Glib itself is responsible for data returned from g_dir_read_name */
01209                 }
01210                 g_dir_close(dir);
01211                 return TRUE;
01212         }
01213         return FALSE;
01214 }
01215 
01216 /* SCHEMA DESTRUCTORS */
01217 
01218 /* Free type names, collect all MgdSchemaTypeAttr pointers */
01219 static void _get_schema_trash(gpointer key, gpointer val, gpointer userdata)
01220 {
01221         MgdSchemaTypeAttr *stype = (MgdSchemaTypeAttr *) val;
01222         
01223         /* Collect types data pointers*/
01224         if(!g_slist_find(schema_trash, stype)) {
01225                 /* g_debug("Adding %s type to trash", (gchar *)key); */ 
01226                 schema_trash = g_slist_append(schema_trash, stype);
01227         }
01228 }
01229 
01230 /* GOBJECT AND CLASS */
01231 
01232 /* Create new object instance */
01233 static void 
01234 _schema_instance_init(GTypeInstance *instance, gpointer g_class)
01235 {
01236         MidgardSchema *self = (MidgardSchema *) instance;
01237         self->types = g_hash_table_new(g_str_hash, g_str_equal);
01238 }
01239 
01240 /* Finalize  */
01241 static void _midgard_schema_finalize(GObject *object)
01242 {
01243         g_assert(object != NULL); /* just in case */
01244         MidgardSchema *self = (MidgardSchema *) object; 
01245 
01246         g_hash_table_foreach(self->types, _get_schema_trash, NULL);
01247         g_hash_table_destroy(self->types);
01248         
01249         for( ; schema_trash ; schema_trash = schema_trash->next){
01250                 _mgd_schema_type_attr_free((MgdSchemaTypeAttr *) schema_trash->data);
01251         }
01252         g_slist_free(schema_trash);                                     
01253 }
01254 
01255 /* Initialize class */
01256 static void _midgard_schema_class_init(
01257                 gpointer g_class, gpointer g_class_data)
01258 {
01259         GObjectClass *gobject_class = G_OBJECT_CLASS (g_class);
01260         MidgardSchemaClass *klass = MIDGARD_SCHEMA_CLASS (g_class);
01261         
01262         gobject_class->set_property = NULL;
01263         gobject_class->get_property = NULL;
01264         gobject_class->finalize = _midgard_schema_finalize;
01265         
01266         klass->init = midgard_schema_init;
01267         klass->read_dir = midgard_schema_read_dir;
01268         klass->read_file = midgard_schema_read_file;
01269         klass->type_exists = midgard_schema_type_exists;
01270 }
01271 
01272 /* Register MidgardSchema type */
01273 GType
01274 midgard_schema_get_type (void)