source: branches/ms-style/zoo-project/zoo-kernel/server_internal.c @ 910

Last change on this file since 910 was 820, checked in by djay, 8 years ago

Fix issue with nested inputs initialization. Fix issue when returning nested outputs. Fix issue with ulinet.c for building the ZOO-Kernel (thanks to Niroshan Sanjaya and Julien Abt for reporting the issue).

  • Property svn:keywords set to Id
File size: 35.3 KB
Line 
1/*
2 * Author : Gérald Fenoy
3 *
4 *  Copyright 2008-2015 GeoLabs SARL. All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24
25#include "server_internal.h"
26#include "service_internal.h"
27#include "response_print.h"
28#include "mimetypes.h"
29#ifndef WIN32
30#include <dlfcn.h>
31#include <uuid/uuid.h>
32#else
33#include <rpc.h>
34#define ERROR_MSG_MAX_LENGTH 1024
35#endif
36#include <signal.h>
37
38// #include <stdlib.h>
39/*
40 * Compare two file path strings to see if they refer to the same file.
41 *
42 * @param path1 the first file path
43 * @param path2 the second file path
44 *
45 * @return 0 if the files are identical
46 */
47#define PATHBUFSIZE 4096
48int zoo_path_compare(char* path1, char* path2) {
49
50  if (path1 == NULL || path2 == NULL) {
51    return -1;
52  }
53
54  char realpath1[PATHBUFSIZE];
55  char realpath2[PATHBUFSIZE];
56
57#ifdef WIN32
58  int res1 = GetFullPathName(path1, PATHBUFSIZE, realpath1, NULL);
59  int res2 = GetFullPathName(path2, PATHBUFSIZE, realpath2, NULL);
60
61  if (res1 == 0 || res2 == 0) {
62    return -1;
63  }
64  else {
65    return strncasecmp(realpath1, realpath2, PATHBUFSIZE);
66  }
67#else
68  char* ptr1 = realpath(path1, realpath1);
69  char* ptr2 = realpath(path2, realpath2);
70
71  if (ptr1 == NULL || ptr2 == NULL) {
72    return -1;
73  }
74  else {
75    return strncmp(realpath1, realpath2, PATHBUFSIZE);
76  }
77#endif
78}
79
80/**
81 * Detect WPS version used (1.0.0 or 2.0.0).
82 *
83 * @param version number as char* (1.0.0 or 2.0.0)
84 * @return 0 in case of version 1.0.0, 1 for 2.0.0, -1 in other case
85 */
86int getVersionId(const char* version){
87  int schemaId=0;
88  for(;schemaId<2;schemaId++){
89    if(strncasecmp(version,schemas[schemaId][0],5)==0)
90      return schemaId;
91  }
92  return -1;
93}
94
95/**
96 * Generate a UUID.
97 * ref: https://www.ietf.org/rfc/rfc4122.txt / 4.2
98 *
99 * @return a new char* containing the UUID, make sure to free the returned
100 *  resource once used.
101 */
102char *get_uuid(){
103  char *res=(char*)malloc(37*sizeof(char));
104#ifdef WIN32
105  UUID uuid;
106  UuidCreate(&uuid);
107  RPC_CSTR rest = NULL;
108  UuidToString(&uuid,&rest);
109#else
110  uuid_t uuid;
111  uuid_generate_time(uuid);
112  char rest[128];
113  uuid_unparse(uuid,rest);
114#endif
115  sprintf(res,"%s",rest);
116#ifdef WIN32
117  RpcStringFree(&rest);
118#endif
119  return res;
120}
121
122/**
123 * Extract the service identifier from the full service identifier
124 * ie:
125 *  - Full service name: OTB.BandMath
126 *  - Service name: BandMath
127 *
128 * @param conf the maps containing the settings of the main.cfg file
129 * @param conf_dir the full path to the ZOO-Kernel directory
130 * @param identifier the full service name (potentialy including a prefix, ie:
131 *  Prefix.MyService)
132 * @param buffer the resulting service identifier (without any prefix)
133 */
134void parseIdentifier(maps* conf,char* conf_dir,char *identifier,char* buffer){
135  setMapInMaps(conf,"lenv","oIdentifier",identifier);
136  char *lid=zStrdup(identifier);
137  char *saveptr1;
138  char *tmps1=strtok_r(lid,".",&saveptr1);
139  int level=0;
140  char key[25];
141  char levels[18];
142  while(tmps1!=NULL){
143    char *test=zStrdup(tmps1);
144    char* tmps2=(char*)malloc((strlen(test)+2)*sizeof(char));
145    sprintf(key,"sprefix_%d",level);
146    sprintf(tmps2,"%s.",test);
147    sprintf(levels,"%d",level);
148    setMapInMaps(conf,"lenv","level",levels);
149    setMapInMaps(conf,"lenv",key,tmps2);
150    free(tmps2);
151    free(test);
152    level++;
153    tmps1=strtok_r(NULL,".",&saveptr1);
154  }
155  int i=0;
156  sprintf(buffer,"%s",conf_dir);
157  for(i=0;i<level;i++){
158    char *tmp0=zStrdup(buffer);
159    sprintf(key,"sprefix_%d",i);
160    map* tmp00=getMapFromMaps(conf,"lenv",key);
161    if(tmp00!=NULL)
162      sprintf(buffer,"%s/%s",tmp0,tmp00->value);
163    free(tmp0);
164    buffer[strlen(buffer)-1]=0;
165    if(i+1<level){ 
166      #ifdef IGNORE_METAPATH
167        map* tmpMap = createMap("metapath", "");
168      #else 
169        map* tmpMap=getMapFromMaps(conf,"lenv","metapath");
170      #endif     
171      if(tmpMap==NULL || strlen(tmpMap->value)==0){
172        char *tmp01=zStrdup(tmp00->value);
173        tmp01[strlen(tmp01)-1]=0;
174        setMapInMaps(conf,"lenv","metapath",tmp01);
175        free(tmp01);
176        tmp01=NULL;
177      }
178      else{
179        if(tmp00!=NULL && tmpMap!=NULL){
180          char *tmp00s=zStrdup(tmp00->value);
181          tmp00s[strlen(tmp00s)-1]=0;
182          char *value=(char*)malloc((strlen(tmp00s)+strlen(tmpMap->value)+2)*sizeof(char));
183          sprintf(value,"%s/%s",tmpMap->value,tmp00s);
184          setMapInMaps(conf,"lenv","metapath",value);
185          free(value);
186          free(tmp00s);
187          value=NULL;
188        }
189      }
190    }else{
191      char *tmp01=zStrdup(tmp00->value);
192      tmp01[strlen(tmp01)-1]=0;
193      setMapInMaps(conf,"lenv","Identifier",tmp01);
194      free(tmp01);
195    }
196  }
197  char *tmp0=zStrdup(buffer);
198  sprintf(buffer,"%s.zcfg",tmp0);
199  free(tmp0);
200  free(lid);
201}
202
203/**
204 * Converts a hex character to its integer value
205 *
206 * @param ch the char to convert
207 * @return the converted char
208 */
209char from_hex(char ch) {
210  return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
211}
212
213/**
214 * Converts an integer value to its hec character
215 *
216 * @param code the char to convert
217 * @return the converted char
218 */
219char to_hex(char code) {
220  static char hex[] = "0123456789abcdef";
221  return hex[code & 15];
222}
223
224/**
225 * URLEncode an url
226 *
227 * @param str the url to encode
228 * @return a url-encoded version of str
229 * @warning be sure to free() the returned string after use
230 */
231char *url_encode(char *str) {
232  char *pstr = str, *buf = (char*) malloc(strlen(str) * 3 + 1), *pbuf = buf;
233  while (*pstr) {
234    if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') 
235      *pbuf++ = *pstr;
236    else if (*pstr == ' ') 
237      *pbuf++ = '+';
238    else 
239      *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
240    pstr++;
241  }
242  *pbuf = '\0';
243  return buf;
244}
245
246/**
247 * Decode an URLEncoded url
248 *
249 * @param str the URLEncoded url to decode
250 * @return a url-decoded version of str
251 * @warning be sure to free() the returned string after use
252 */
253char *url_decode(char *str) {
254  char *pstr = str, *buf = (char*) malloc(strlen(str) + 1), *pbuf = buf;
255  while (*pstr) {
256    if (*pstr == '%') {
257      if (pstr[1] && pstr[2]) {
258        *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
259        pstr += 2;
260      }
261    } else if (*pstr == '+') { 
262      *pbuf++ = ' ';
263    } else {
264      *pbuf++ = *pstr;
265    }
266    pstr++;
267  }
268  *pbuf = '\0';
269  return buf;
270}
271
272/**
273 * Verify if a given language is listed in the lang list defined in the [main]
274 * section of the main.cfg file.
275 *
276 * @param conf the map containing the settings from the main.cfg file
277 * @param str the specific language
278 * @return 1 if the specific language is listed, -1 in other case.
279 */
280int isValidLang(maps* conf,const char *str){
281  map *tmpMap=getMapFromMaps(conf,"main","language");
282  char *tmp0=NULL,*tmp=NULL,*tmp1=NULL;
283  if(tmpMap!=NULL)
284    tmp0=zStrdup(tmpMap->value);
285  tmpMap=getMapFromMaps(conf,"main","lang");
286  if(tmpMap!=NULL)
287    tmp=zStrdup(tmpMap->value);
288  if(tmp0!=NULL && tmp!=NULL){
289    tmp1=(char*)malloc((strlen(tmp0)+strlen(tmp)+2)*sizeof(char));
290    sprintf(tmp1,"%s,%s",tmp0,tmp);
291    free(tmp0);
292    free(tmp);
293  }else{
294    if(tmp!=NULL){
295      tmp1=zStrdup(tmp);
296      free(tmp);
297    }else{
298      if(tmp0!=NULL){
299        tmp1=zStrdup(tmp0);
300        free(tmp0);
301      }
302    }
303  }
304  char *pToken,*saveptr;
305  pToken=strtok_r(tmp1,",",&saveptr);
306  int res=-1;
307  while(pToken!=NULL){
308    if(strcasecmp(str,pToken)==0){
309      res=1;
310      break;
311    }
312    pToken=strtok_r(NULL,",",&saveptr);
313  }
314  if(tmp1!=NULL)
315    free(tmp1);
316  return res;
317}
318
319
320/**
321 * Access the value of the encoding key in a maps
322 *
323 * @param m the maps to search for the encoding key
324 * @return the value of the encoding key in a maps if encoding key exists,
325 *  "UTF-8" in other case.
326 */
327char* getEncoding(maps* m){
328  if(m!=NULL){
329    map* tmp=getMap(m->content,"encoding");
330    if(tmp!=NULL){
331      return tmp->value;
332    }
333    else
334      return (char*)"UTF-8";
335  }
336  else
337    return (char*)"UTF-8"; 
338}
339
340/**
341 * Access the value of the version key in a maps
342 *
343 * @param m the maps to search for the version key
344 * @return the value of the version key in a maps if encoding key exists,
345 *  "1.0.0" in other case.
346 */
347char* getVersion(maps* m){
348  if(m!=NULL){
349    map* tmp=getMap(m->content,"version");
350    if(tmp!=NULL){
351      return tmp->value;
352    }
353    else
354      return (char*)"1.0.0";
355  }
356  else
357    return (char*)"1.0.0";
358}
359
360/**
361 * Read a file generated by a service.
362 *
363 * @param m the conf maps
364 * @param content the output item
365 * @param filename the file to read
366 */
367void readGeneratedFile(maps* m,map* content,char* filename){
368  FILE * file=fopen(filename,"rb");
369  if(file==NULL){
370    fprintf(stderr,"Failed to open file %s for reading purpose.\n",filename);
371    setMapInMaps(m,"lenv","message","Unable to read produced file. Please try again later");
372    return ;
373  }
374  fseek(file, 0, SEEK_END);
375  long count = ftell(file);
376  rewind(file);
377  struct stat file_status; 
378  stat(filename, &file_status);
379  map* tmpMap1=getMap(content,"value");
380  if(tmpMap1==NULL){
381    addToMap(content,"value","");
382    tmpMap1=getMap(content,"value");
383  }
384  free(tmpMap1->value);
385  tmpMap1->value=(char*) malloc((count+1)*sizeof(char)); 
386  fread(tmpMap1->value,1,count,file);
387  tmpMap1->value[count]=0;
388  fclose(file);
389  char rsize[1000];
390  sprintf(rsize,"%ld",count);
391  addToMap(content,"size",rsize);
392}
393
394
395/**
396 * Write a file from value and length
397 *
398 * @param fname the file name
399 * @param val the value
400 * @param length the value length
401 */
402int writeFile(char* fname,char* val,int length){
403  FILE* of=fopen(fname,"wb");
404  if(of==NULL){
405    return -1;
406  }
407  size_t ret=fwrite(val,sizeof(char),length,of);
408  if(ret<length){
409    fprintf(stderr,"Write error occurred!\n");
410    fclose(of);
411    return -1;
412  }
413  fclose(of);
414  return 1;
415}
416
417/**
418 * Dump all values in a maps as files
419 *
420 * @param main_conf the maps containing the settings of the main.cfg file
421 * @param in the maps containing values to dump as files
422 */
423void dumpMapsValuesToFiles(maps** main_conf,maps** in){
424  map* tmpPath=getMapFromMaps(*main_conf,"main","tmpPath");
425  map* tmpSid=getMapFromMaps(*main_conf,"lenv","usid");
426  maps* inputs=*in;
427  int length=0;
428  while(inputs!=NULL){
429    if(getMap(inputs->content,"mimeType")!=NULL &&
430       getMap(inputs->content,"cache_file")==NULL){
431      map* cMap=inputs->content;
432      if(getMap(cMap,"length")!=NULL){
433        map* tmpLength=getMap(cMap,"length");
434        int len=atoi(tmpLength->value);
435        int k=0;
436        for(k=0;k<len;k++){
437          map* cMimeType=getMapArray(cMap,"mimeType",k);
438          map* cValue=getMapArray(cMap,"value",k);
439          map* cSize=getMapArray(cMap,"size",k);
440          char file_ext[32];
441          getFileExtension(cMimeType != NULL ? cMimeType->value : NULL, file_ext, 32);
442          char* val=(char*)malloc((strlen(tmpPath->value)+strlen(inputs->name)+strlen(tmpSid->value)+strlen(file_ext)+16)*sizeof(char));
443          sprintf(val,"%s/Input_%s_%s_%d.%s",tmpPath->value,inputs->name,tmpSid->value,k,file_ext);
444          length=0;
445          if(cSize!=NULL){
446            length=atoi(cSize->value);
447          }
448          writeFile(val,cValue->value,length);
449          setMapArray(cMap,"cache_file",k,val);
450          free(val);
451        }
452      }else{
453        int length=0;
454        map* cMimeType=getMap(cMap,"mimeType");
455        map* cValue=getMap(cMap,"value");
456        map* cSize=getMap(cMap,"size");
457        char file_ext[32];
458        getFileExtension(cMimeType != NULL ? cMimeType->value : NULL, file_ext, 32);
459        char *val=(char*)malloc((strlen(tmpPath->value)+strlen(inputs->name)+strlen(tmpSid->value)+strlen(file_ext)+16)*sizeof(char));
460        sprintf(val,"%s/Input_%s_%s_%d.%s",tmpPath->value,inputs->name,tmpSid->value,0,file_ext);
461        if(cSize!=NULL){
462          length=atoi(cSize->value);
463        }
464        writeFile(val,cValue->value,length);
465        addToMap(cMap,"cache_file",val);
466        free(val);
467      }
468    }
469    inputs=inputs->next;
470  }
471}
472
473
474/**
475 * Base64 encoding of a char*
476 *
477 * @param input the value to encode
478 * @param length the value length
479 * @return the buffer containing the base64 value
480 * @warning make sure to free the returned value
481 */
482char *base64(const char *input, int length)
483{
484  BIO *bmem, *b64;
485  BUF_MEM *bptr;
486
487  b64 = BIO_new(BIO_f_base64());
488  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
489  bmem = BIO_new(BIO_s_mem());
490  b64 = BIO_push(b64, bmem);
491  BIO_write(b64, input, length);
492  BIO_flush(b64);
493  BIO_get_mem_ptr(b64, &bptr);
494
495  char *buff = (char *)malloc((bptr->length+1)*sizeof(char));
496  memcpy(buff, bptr->data, bptr->length);
497  buff[bptr->length] = 0;
498
499  BIO_free_all(b64);
500
501  return buff;
502}
503
504/**
505 * Base64 decoding of a char*
506 *
507 * @param input the value to decode
508 * @param length the value length
509 * @param red the value length
510 * @return the buffer containing the base64 value
511 * @warning make sure to free the returned value
512 */
513char *base64d(const char *input, int length,int* red)
514{
515  BIO *b64, *bmem;
516
517  char *buffer = (char *)malloc(length);
518  if(buffer){
519    memset(buffer, 0, length);
520    b64 = BIO_new(BIO_f_base64());
521    if(b64){
522      bmem = BIO_new_mem_buf((unsigned char*)input,length);
523      bmem = BIO_push(b64, bmem);
524      *red=BIO_read(bmem, buffer, length);
525      buffer[length-1]=0;
526      BIO_free_all(bmem);
527    }
528  }
529  return buffer;
530}
531
532/**
533 * Read Base64 value and split it value by lines of 64 char.
534 *
535 * @param in the map containing the value to split
536 */
537void readBase64(map **in){
538  char *res = NULL;
539  char *curs = (*in)->value;
540  int i = 0;
541  for (i = 0; i <= strlen ((*in)->value) / 64;
542       i++)
543    {
544      if (res == NULL)
545        res =
546          (char *) malloc (65 * sizeof (char));
547      else
548        res =
549          (char *) realloc (res,
550                            (((i + 1) * 65) +
551                             i) * sizeof (char));
552      int csize = i * 65;
553      strncpy (res + csize, curs, 64);
554      if (i == strlen ((*in)->value) / 64)
555        strcat (res, "\n\0");
556      else
557        {
558          strncpy (res + (((i + 1) * 64) + i),
559                   "\n\0", 2);
560          curs += 64;
561        }
562    }
563  free ((*in)->value);
564  (*in)->value = zStrdup (res);
565  free (res);
566}
567
568
569/**
570 * Add the default values defined in the zcfg to a maps.
571 *
572 * @param out the maps containing the inputs or outputs given in the initial
573 *  HTTP request
574 * @param in the description of all inputs or outputs available for a service
575 * @param m the maps containing the settings of the main.cfg file
576 * @param type 0 for inputs and 1 for outputs
577 * @param err the map to store potential missing mandatory input parameters or
578 *  wrong output names depending on the type.
579 * @return "" if no error was detected, the name of last input or output causing
580 *  an error.
581 */
582char* addDefaultValues(maps** out,elements* in,maps* m,int type,map** err){
583  map *res=*err;
584  elements* tmpInputs=in;
585  elements* tmpInputss=NULL;
586  maps* out1=*out;
587  maps* out1s=NULL;
588  char *result=NULL;
589  int nb=0;
590  int inb=0;
591 loopOnInputs:
592  if(type==1){
593    while(out1!=NULL){
594      if(getElements(in,out1->name)==NULL){
595        if(res==NULL){
596          res=createMap("value",out1->name);
597        }else{
598          setMapArray(res,"value",nb,out1->name);
599        }
600        nb++;
601        result=out1->name;
602      }
603      inb++;
604      out1=out1->next;
605    }
606    if(res!=NULL){
607      fflush(stderr);
608      *err=res;
609      return result;
610    }
611    if(out1==NULL && inb>=1)
612      out1=*out;
613  }
614  while(tmpInputs!=NULL){
615    maps *tmpMaps=getMaps(out1,tmpInputs->name);
616    if(tmpMaps==NULL){
617      maps* tmpMaps2=createMaps(tmpInputs->name);
618      if(type==0){
619        map* tmpMapMinO=getMap(tmpInputs->content,"minOccurs");
620        if(tmpMapMinO!=NULL){
621          if(atoi(tmpMapMinO->value)>=1){
622            freeMaps(&tmpMaps2);
623            free(tmpMaps2);
624            if(res==NULL){
625              res=createMap("value",tmpInputs->name);
626            }else{
627              setMapArray(res,"value",nb,tmpInputs->name);
628            }
629            nb++;
630            result=tmpInputs->name;
631          }
632          else{
633            if(tmpMaps2->content==NULL)
634              tmpMaps2->content=createMap("minOccurs",tmpMapMinO->value);
635            else
636              addToMap(tmpMaps2->content,"minOccurs",tmpMapMinO->value);
637          }
638        }
639        if(res==NULL){
640          map* tmpMaxO=getMap(tmpInputs->content,"maxOccurs");
641          if(tmpMaxO!=NULL){
642            if(tmpMaps2->content==NULL)
643              tmpMaps2->content=createMap("maxOccurs",tmpMaxO->value);
644            else
645              addToMap(tmpMaps2->content,"maxOccurs",tmpMaxO->value);
646          }
647          map* tmpMaxMB=getMap(tmpInputs->content,"maximumMegabytes");
648          if(tmpMaxMB!=NULL){
649            if(tmpMaps2->content==NULL)
650              tmpMaps2->content=createMap("maximumMegabytes",tmpMaxMB->value);
651            else
652              addToMap(tmpMaps2->content,"maximumMegabytes",tmpMaxMB->value);
653          }
654        }
655      }
656     
657      if(res==NULL){
658        iotype* tmpIoType=tmpInputs->defaults;
659        if(tmpIoType!=NULL){
660          map* tmpm=tmpIoType->content;
661          while(tmpm!=NULL){
662            if(tmpMaps2->content==NULL)
663              tmpMaps2->content=createMap(tmpm->name,tmpm->value);
664            else{
665              addToMap(tmpMaps2->content,tmpm->name,tmpm->value);
666            }
667            tmpm=tmpm->next;
668          }
669        }
670        if(tmpMaps2->content==NULL){
671          tmpMaps2->content=createMap("inRequest","false");
672          dumpMaps(tmpMaps2);
673        }
674        else
675          addToMap(tmpMaps2->content,"inRequest","false");
676        if(type==0){
677          map *tmpMap=getMap(tmpMaps2->content,"value");
678          if(tmpMap==NULL)
679            addToMap(tmpMaps2->content,"value","NULL");
680        }
681        elements* tmpElements=getElements(in,tmpMaps2->name);
682        if(tmpElements!=NULL && tmpElements->child!=NULL){
683          char *res=addDefaultValues(&tmpMaps2->child,tmpElements->child,m,type,err);
684          if(strlen(res)>0){
685            return res;
686          }
687        }
688
689        if(out1==NULL){
690          *out=dupMaps(&tmpMaps2);
691          out1=*out;
692        }
693        else
694          addMapsToMaps(&out1,tmpMaps2);
695        freeMap(&tmpMaps2->content);
696        free(tmpMaps2->content);
697        tmpMaps2->content=NULL;
698        freeMaps(&tmpMaps2);
699        free(tmpMaps2);
700        tmpMaps2=NULL;
701      }
702    }
703    else { 
704      iotype* tmpIoType=NULL;
705      if(tmpMaps->content!=NULL){
706        tmpIoType=getIoTypeFromElement(tmpInputs,tmpInputs->name,
707                                       tmpMaps->content);
708        if(type==0) {
709          /**
710           * In case of an Input maps, then add the minOccurs and maxOccurs to the
711           * content map.
712           */
713          map* tmpMap1=getMap(tmpInputs->content,"minOccurs");
714          if(tmpMap1!=NULL){
715            if(tmpMaps->content==NULL)
716              tmpMaps->content=createMap("minOccurs",tmpMap1->value);
717            else
718              addToMap(tmpMaps->content,"minOccurs",tmpMap1->value);
719          }
720          map* tmpMaxO=getMap(tmpInputs->content,"maxOccurs");
721          if(tmpMaxO!=NULL){
722            if(tmpMaps->content==NULL)
723              tmpMaps->content=createMap("maxOccurs",tmpMaxO->value);
724            else
725              addToMap(tmpMaps->content,"maxOccurs",tmpMaxO->value);
726          }
727          map* tmpMaxMB=getMap(tmpInputs->content,"maximumMegabytes");
728          if(tmpMaxMB!=NULL){
729            if(tmpMaps->content==NULL)
730              tmpMaps->content=createMap("maximumMegabytes",tmpMaxMB->value);
731            else
732              addToMap(tmpMaps->content,"maximumMegabytes",tmpMaxMB->value);
733          }
734          /**
735           * Parsing BoundingBoxData, fill the following map and then add it to
736           * the content map of the Input maps:
737           * lowerCorner, upperCorner, srs and dimensions
738           * cf. parseBoundingBox
739           */
740          if(tmpInputs->format!=NULL && strcasecmp(tmpInputs->format,"BoundingBoxData")==0){
741            maps* tmpI=getMaps(*out,tmpInputs->name);
742            if(tmpI!=NULL){
743              map* tmpV=getMap(tmpI->content,"value");
744              if(tmpV!=NULL){
745                char *tmpVS=strdup(tmpV->value);
746                map* tmp=parseBoundingBox(tmpVS);
747                free(tmpVS);
748                map* tmpC=tmp;
749                while(tmpC!=NULL){
750                  addToMap(tmpMaps->content,tmpC->name,tmpC->value);
751                  tmpC=tmpC->next;
752                }
753                freeMap(&tmp);
754                free(tmp);
755              }
756            }
757          }
758        }
759      }else{
760        if(tmpInputs!=NULL){
761          tmpIoType=tmpInputs->defaults;
762        }
763      }
764
765      if(tmpIoType!=NULL){
766        map* tmpContent=tmpIoType->content;
767        map* cval=NULL;
768        int hasPassed=-1;
769        while(tmpContent!=NULL){
770          if((cval=getMap(tmpMaps->content,tmpContent->name))==NULL){
771#ifdef DEBUG
772            fprintf(stderr,"addDefaultValues %s => %s\n",tmpContent->name,tmpContent->value);
773#endif
774            if(tmpMaps->content==NULL)
775              tmpMaps->content=createMap(tmpContent->name,tmpContent->value);
776            else
777              addToMap(tmpMaps->content,tmpContent->name,tmpContent->value);
778           
779            if(hasPassed<0 && type==0 && getMap(tmpMaps->content,"isArray")!=NULL){
780              map* length=getMap(tmpMaps->content,"length");
781              int i;
782              char *tcn=strdup(tmpContent->name);
783              for(i=1;i<atoi(length->value);i++){
784#ifdef DEBUG
785                dumpMap(tmpMaps->content);
786                fprintf(stderr,"addDefaultValues %s_%d => %s\n",tcn,i,tmpContent->value);
787#endif
788                int len=strlen((char*) tcn);
789                char *tmp1=(char *)malloc((len+10)*sizeof(char));
790                sprintf(tmp1,"%s_%d",tcn,i);
791#ifdef DEBUG
792                fprintf(stderr,"addDefaultValues %s => %s\n",tmp1,tmpContent->value);
793#endif
794                addToMap(tmpMaps->content,tmp1,tmpContent->value);
795                free(tmp1);
796                hasPassed=1;
797              }
798              free(tcn);
799            }
800          }
801          tmpContent=tmpContent->next;
802        }
803#ifdef USE_MS
804        /**
805         * check for useMapServer presence
806         */
807        if(tmpIoType!=NULL){
808          map* tmpCheck=getMap(tmpIoType->content,"useMapServer");
809          if(tmpCheck!=NULL){
810            // Get the default value
811            tmpIoType=getIoTypeFromElement(tmpInputs,tmpInputs->name,NULL);
812            tmpCheck=getMap(tmpMaps->content,"mimeType");
813            addToMap(tmpMaps->content,"requestedMimeType",tmpCheck->value);
814            map* cursor=tmpIoType->content;
815            while(cursor!=NULL){
816              addToMap(tmpMaps->content,cursor->name,cursor->value);
817              cursor=cursor->next;
818            }
819         
820            cursor=tmpInputs->content;
821            while(cursor!=NULL){
822              if(strcasecmp(cursor->name,"Title")==0 ||
823                 strcasecmp(cursor->name,"Abstract")==0)
824                addToMap(tmpMaps->content,cursor->name,cursor->value);
825              cursor=cursor->next;
826            }
827          }
828        }
829#endif
830      }
831      if(tmpMaps->content==NULL)
832        tmpMaps->content=createMap("inRequest","true");
833      else
834        addToMap(tmpMaps->content,"inRequest","true");
835      elements* tmpElements=getElements(in,tmpMaps->name);
836      if(tmpElements!=NULL && tmpElements->child!=NULL){
837        char *res=addDefaultValues(&tmpMaps->child,tmpElements->child,m,type,err);
838        if(strlen(res)>0){
839          return res;
840        }
841      }
842    }
843    tmpInputs=tmpInputs->next;
844  }
845  if(tmpInputss!=NULL){
846    out1=out1s;
847    tmpInputs=tmpInputss;
848    tmpInputss=NULL;
849    out1s=NULL;
850    goto loopOnInputs;
851  }
852  if(res!=NULL){
853    *err=res;
854    return result;
855  }
856  return "";
857}
858
859/**
860 * Access the last error message returned by the OS when trying to dynamically
861 * load a shared library.
862 *
863 * @return the last error message
864 * @warning The character string returned from getLastErrorMessage resides
865 * in a static buffer. The application should not write to this
866 * buffer or attempt to free() it.
867 */ 
868char* getLastErrorMessage() {                                             
869#ifdef WIN32
870  LPVOID lpMsgBuf;
871  DWORD errCode = GetLastError();
872  static char msg[ERROR_MSG_MAX_LENGTH];
873  size_t i;
874 
875  DWORD length = FormatMessage(
876                               FORMAT_MESSAGE_ALLOCATE_BUFFER | 
877                               FORMAT_MESSAGE_FROM_SYSTEM |
878                               FORMAT_MESSAGE_IGNORE_INSERTS,
879                               NULL,
880                               errCode,
881                               MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
882                               (LPTSTR) &lpMsgBuf,
883                               0, NULL );       
884 
885#ifdef UNICODE         
886  wcstombs_s( &i, msg, ERROR_MSG_MAX_LENGTH,
887              (wchar_t*) lpMsgBuf, _TRUNCATE );
888#else
889  strcpy_s( msg, ERROR_MSG_MAX_LENGTH,
890            (char *) lpMsgBuf );               
891#endif 
892  LocalFree(lpMsgBuf);
893
894  return msg;
895#else
896  return dlerror();
897#endif
898}
899
900#include <dirent.h>
901#ifndef RELY_ON_DB
902/**
903 * Read the Result file (.res).
904 *
905 * @param conf the maps containing the setting of the main.cfg file
906 * @param pid the service identifier (usid key from the [lenv] section)
907 */
908void readFinalRes(maps* conf,char* pid,map* statusInfo){
909  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
910  char* fbkpid =
911    (char *)
912    malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
913  sprintf (fbkpid, "%s/%s.res", r_inputs->value, pid);
914  struct stat file_status;
915  int istat = stat (fbkpid, &file_status);
916  if (istat == 0 && file_status.st_size > 0)
917    {
918      maps *res = (maps *) malloc (MAPS_SIZE);
919      conf_read (fbkpid, res);
920      res->child=NULL;
921      map* status=getMapFromMaps(res,"status","status");
922      addToMap(statusInfo,"Status",status->value);
923      freeMaps(&res);
924      free(res);
925    }
926  else
927    addToMap(statusInfo,"Status","Failed"); 
928  free(fbkpid);
929}
930
931/**
932 * Check if a service is running.
933 *
934 * @param conf the maps containing the setting of the main.cfg file
935 * @param pid the unique service identifier (usid from the lenv section)
936 * @return 1 in case the service is still running, 0 otherwise
937 */
938int isRunning(maps* conf,char* pid){
939  int res=0;
940  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
941  char* fbkpid =
942    (char *)
943    malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
944  sprintf (fbkpid, "%s/%s.pid", r_inputs->value, pid);
945  FILE* f0 = fopen (fbkpid, "r");
946  if(f0!=NULL){
947    fclose(f0);
948    res=1;
949  }
950  free(fbkpid);
951  return res;
952}
953#else
954#include "sqlapi.h"
955#endif
956
957/**
958 * Run GetStatus requests.
959 *
960 * @param conf the maps containing the setting of the main.cfg file
961 * @param pid the service identifier (usid key from the [lenv] section)
962 * @param req the request (GetStatus / GetResult)
963 */
964void runGetStatus(maps* conf,char* pid,char* req){
965  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
966  char *sid=getStatusId(conf,pid);
967  if(sid==NULL){
968    errorException (conf, _("The JobID from the request does not match any of the Jobs running on this server"),
969                    "NoSuchJob", pid);
970  }else{
971    map* statusInfo=createMap("JobID",pid);
972    if(isRunning(conf,pid)>0){
973      if(strncasecmp(req,"GetResult",strlen(req))==0){
974        errorException (conf, _("The result for the requested JobID has not yet been generated. "),
975                        "ResultNotReady", pid);
976        return;
977      }
978      else
979        if(strncasecmp(req,"GetStatus",strlen(req))==0){
980          addToMap(statusInfo,"Status","Running");
981          char* tmpStr=_getStatus(conf,pid);
982          if(tmpStr!=NULL && strncmp(tmpStr,"-1",2)!=0){
983            char *tmpStr1=strdup(tmpStr);
984            char *tmpStr0=strdup(strstr(tmpStr,"|")+1);
985            free(tmpStr);
986            tmpStr1[strlen(tmpStr1)-strlen(tmpStr0)-1]='\0';
987            addToMap(statusInfo,"PercentCompleted",tmpStr1);
988            addToMap(statusInfo,"Message",tmpStr0);
989            free(tmpStr0);
990            free(tmpStr1);
991          }
992        }
993    }
994    else{
995      if(strncasecmp(req,"GetResult",strlen(req))==0){
996        char* result=_getStatusFile(conf,pid);
997        if(result!=NULL){
998          char *encoding=getEncoding(conf);
999          printf("Content-Type: text/xml; charset=%s\r\nStatus: 200 OK\r\n\r\n",encoding);
1000          printf("%s",result);
1001          fflush(stdout);
1002          freeMap(&statusInfo);
1003          free(statusInfo);
1004          return;
1005        }else{
1006          errorException (conf, _("The result for the requested JobID has not yet been generated. "),
1007                          "ResultNotReady", pid);
1008          freeMap(&statusInfo);
1009          free(statusInfo);
1010          return;
1011        }
1012      }else
1013        if(strncasecmp(req,"GetStatus",strlen(req))==0){
1014          readFinalRes(conf,pid,statusInfo);
1015          char* tmpStr=_getStatus(conf,pid);
1016          if(tmpStr!=NULL && strncmp(tmpStr,"-1",2)!=0){
1017            char *tmpStr1=strdup(tmpStr);
1018            char *tmpStr0=strdup(strstr(tmpStr,"|")+1);
1019            free(tmpStr);
1020            tmpStr1[strlen(tmpStr1)-strlen(tmpStr0)-1]='\0';
1021            addToMap(statusInfo,"PercentCompleted",tmpStr1);
1022            addToMap(statusInfo,"Message",tmpStr0);
1023            free(tmpStr0);
1024            free(tmpStr1);
1025          }
1026        }
1027    }
1028    printStatusInfo(conf,statusInfo,req);
1029    freeMap(&statusInfo);
1030    free(statusInfo);
1031  }
1032  return;
1033}
1034
1035/**
1036 * Run Dismiss requests.
1037 *
1038 * @param conf the maps containing the setting of the main.cfg file
1039 * @param pid the service identifier (usid key from the [lenv] section)
1040 */
1041void runDismiss(maps* conf,char* pid){
1042  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
1043  char *sid=getStatusId(conf,pid);
1044  if(sid==NULL){
1045    errorException (conf, _("The JobID from the request does not match any of the Jobs running on this server"),
1046                    "NoSuchJob", pid);
1047  }else{
1048    // We should send the Dismiss request to the target host if it differs
1049    char* fbkpid =
1050      (char *)
1051      malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
1052    sprintf (fbkpid, "%s/%s.pid", r_inputs->value, pid);
1053    FILE* f0 = fopen (fbkpid, "r");
1054    if(f0!=NULL){
1055      long flen;
1056      char *fcontent;
1057      fseek (f0, 0, SEEK_END);
1058      flen = ftell (f0);
1059      fseek (f0, 0, SEEK_SET);
1060      fcontent = (char *) malloc ((flen + 1) * sizeof (char));
1061      fread(fcontent,flen,1,f0);
1062      fcontent[flen]=0;
1063      fclose(f0);
1064#ifndef WIN32
1065      kill(atoi(fcontent),SIGKILL);
1066#else
1067      HANDLE myZooProcess=OpenProcess(PROCESS_ALL_ACCESS,false,atoi(fcontent));
1068      TerminateProcess(myZooProcess,1);
1069      CloseHandle(myZooProcess);
1070#endif
1071      free(fcontent);
1072    }
1073    free(fbkpid);
1074    struct dirent *dp;
1075    DIR *dirp = opendir(r_inputs->value);
1076    char fileName[1024];
1077    int hasFile=-1;
1078    if(dirp!=NULL){
1079      while ((dp = readdir(dirp)) != NULL){
1080#ifdef DEBUG
1081        fprintf(stderr,"File : %s searched : %s\n",dp->d_name,tmp);
1082#endif
1083        if(strstr(dp->d_name,pid)!=0){
1084          sprintf(fileName,"%s/%s",r_inputs->value,dp->d_name);
1085          if(unlink(fileName)!=0){
1086            errorException (conf, 
1087                            _("The job cannot be removed, a file cannot be removed"),
1088                            "NoApplicableCode", NULL);
1089            return;
1090          }
1091        }
1092      }
1093    }
1094#ifdef RELY_ON_DB
1095    removeService(conf,pid);
1096#endif
1097    map* statusInfo=createMap("JobID",pid);
1098    addToMap(statusInfo,"Status","Dismissed");
1099    printStatusInfo(conf,statusInfo,"Dismiss");
1100    free(statusInfo);
1101  }
1102  return;
1103}
1104
1105extern int getServiceFromFile (maps *, const char *, service **);
1106
1107/**
1108 * Parse the service file using getServiceFromFile or use getServiceFromYAML
1109 * if YAML support was activated.
1110 *
1111 * @param conf the conf maps containing the main.cfg settings
1112 * @param file the file name to parse
1113 * @param service the service to update witht the file content
1114 * @param name the service name
1115 * @return true if the file can be parsed or false
1116 * @see getServiceFromFile, getServiceFromYAML
1117 */
1118int readServiceFile (maps * conf, char *file, service ** service, char *name){
1119  int t = getServiceFromFile (conf, file, service);
1120#ifdef YAML
1121  if (t < 0){
1122    t = getServiceFromYAML (conf, file, service, name);
1123  }
1124#endif
1125  return t;
1126}
1127
1128/**
1129 * Create the profile registry.
1130 *
1131 * The profile registry is optional (created only if the registry key is
1132 * available in the [main] section of the main.cfg file) and can be used to
1133 * store the profiles hierarchy. The registry is a directory which should
1134 * contain the following sub-directories:
1135 *  * concept: direcotry containing .html files describing concept
1136 *  * generic: directory containing .zcfg files for wps:GenericProcess
1137 *  * implementation: directory containing .zcfg files for wps:Process
1138 *
1139 * @param m the conf maps containing the main.cfg settings
1140 * @param r the registry to update
1141 * @param reg_dir the resgitry
1142 * @return 0 if the resgitry is null or was correctly updated, -1 on failure
1143 */
1144int createRegistry (maps* m,registry ** r, char *reg_dir)
1145{
1146  char registryKeys[3][15]={
1147    "concept",
1148    "generic",
1149    "implementation"
1150  };
1151  int scount = 0,i=0;
1152  if (reg_dir == NULL)
1153    return 0;
1154  for(i=0;i<3;i++){
1155    char * tmpName =
1156      (char *) malloc ((strlen (reg_dir) + strlen (registryKeys[i]) + 2) *
1157                       sizeof (char));
1158    sprintf (tmpName, "%s/%s", reg_dir, registryKeys[i]);
1159   
1160    DIR *dirp1 = opendir (tmpName);
1161    if(dirp1==NULL){
1162      setMapInMaps(m,"lenv","message",_("Unable to open the registry directory."));
1163      setMapInMaps(m,"lenv","type","InternalError");
1164      return -1;
1165    }
1166    struct dirent *dp1;
1167    while ((dp1 = readdir (dirp1)) != NULL){
1168      char* extn = strstr(dp1->d_name, ".zcfg");
1169      if(dp1->d_name[0] != '.' && extn != NULL && strlen(extn) == 5)
1170        {
1171          int t;
1172          char *tmps1=
1173            (char *) malloc ((strlen (tmpName) + strlen (dp1->d_name) + 2) *
1174                             sizeof (char));
1175          sprintf (tmps1, "%s/%s", tmpName, dp1->d_name);
1176          char *tmpsn = zStrdup (dp1->d_name);
1177          tmpsn[strlen (tmpsn) - 5] = 0;
1178          service* s1 = (service *) malloc (SERVICE_SIZE);
1179          if (s1 == NULL)
1180            {
1181              setMapInMaps(m,"lenv","message",_("Unable to allocate memory."));
1182              setMapInMaps(m,"lenv","type","InternalError");
1183              return -2;
1184            }
1185          t = readServiceFile (m, tmps1, &s1, tmpsn);
1186          free (tmpsn);
1187          if (t < 0)
1188            {
1189              map *tmp00 = getMapFromMaps (m, "lenv", "message");
1190              char tmp01[1024];
1191              if (tmp00 != NULL)
1192                sprintf (tmp01, _("Unable to parse the ZCFG file: %s (%s)"),
1193                         dp1->d_name, tmp00->value);
1194              else
1195                sprintf (tmp01, _("Unable to parse the ZCFG file: %s."),
1196                         dp1->d_name);
1197              setMapInMaps(m,"lenv","message",tmp01);
1198              setMapInMaps(m,"lenv","type","InternalError");
1199              return -1;
1200            }
1201          if(strncasecmp(registryKeys[i],"implementation",14)==0){
1202            inheritance(*r,&s1);
1203          }
1204          addServiceToRegistry(r,registryKeys[i],s1);
1205          freeService (&s1);
1206          free (s1);
1207          scount++;
1208        }
1209    }
1210    (void) closedir (dirp1);
1211  }
1212  return 0;
1213}
1214
1215#ifdef WIN32
1216/**
1217 * Create a KVP request for executing background task.
1218 * TODO: use the XML request in case of input POST request.
1219 *
1220 * @param m the maps containing the parameters from the main.cfg file
1221 * @param length the total length of the KVP parameters
1222 * @param type
1223 */
1224char* getMapsAsKVP(maps* m,int length,int type){
1225  char *dataInputsKVP=(char*) malloc(length*sizeof(char));
1226  char *dataInputsKVPi=NULL;
1227  maps* curs=m;
1228  int i=0;
1229  while(curs!=NULL){
1230    map *inRequest=getMap(curs->content,"inRequest");
1231    map *hasLength=getMap(curs->content,"length");
1232    if((inRequest!=NULL && strncasecmp(inRequest->value,"true",4)==0) ||
1233       inRequest==NULL){
1234      if(i==0)
1235        if(type==0){
1236          sprintf(dataInputsKVP,"%s=",curs->name);
1237          if(hasLength!=NULL){
1238            dataInputsKVPi=(char*)malloc((strlen(curs->name)+2)*sizeof(char));
1239            sprintf(dataInputsKVPi,"%s=",curs->name);
1240          }
1241        }
1242        else
1243          sprintf(dataInputsKVP,"%s",curs->name);
1244      else{
1245        char *temp=zStrdup(dataInputsKVP);
1246        if(type==0)
1247          sprintf(dataInputsKVP,"%s;%s=",temp,curs->name);
1248        else
1249          sprintf(dataInputsKVP,"%s;%s",temp,curs->name);
1250      }
1251      map* icurs=curs->content;
1252      if(type==0){
1253        char *temp=zStrdup(dataInputsKVP);
1254        if(getMap(curs->content,"xlink:href")!=NULL)
1255          sprintf(dataInputsKVP,"%sReference",temp);
1256        else{
1257          if(hasLength!=NULL){
1258            int j;
1259            for(j=0;j<atoi(hasLength->value);j++){
1260              map* tmp0=getMapArray(curs->content,"value",j);
1261              if(j==0)
1262                free(temp);
1263              temp=zStrdup(dataInputsKVP);
1264              if(j==0)
1265                sprintf(dataInputsKVP,"%s%s",temp,tmp0->value);
1266              else
1267                sprintf(dataInputsKVP,"%s;%s%s",temp,dataInputsKVPi,tmp0->value);
1268            }
1269          }
1270          else
1271            sprintf(dataInputsKVP,"%s%s",temp,icurs->value);
1272        }
1273        free(temp);
1274      }
1275      while(icurs!=NULL){
1276        if(strncasecmp(icurs->name,"value",5)!=0 &&
1277           strncasecmp(icurs->name,"mimeType_",9)!=0 &&
1278           strncasecmp(icurs->name,"dataType_",9)!=0 &&
1279           strncasecmp(icurs->name,"size",4)!=0 &&
1280           strncasecmp(icurs->name,"length",4)!=0 &&
1281           strncasecmp(icurs->name,"isArray",7)!=0 &&
1282           strcasecmp(icurs->name,"Reference")!=0 &&
1283           strcasecmp(icurs->name,"minOccurs")!=0 &&
1284           strcasecmp(icurs->name,"maxOccurs")!=0 &&
1285           strncasecmp(icurs->name,"fmimeType",9)!=0 &&
1286           strcasecmp(icurs->name,"inRequest")!=0){
1287          char *itemp=zStrdup(dataInputsKVP);
1288          if(strcasecmp(icurs->name,"xlink:href")!=0)
1289            sprintf(dataInputsKVP,"%s@%s=%s",itemp,icurs->name,icurs->value);
1290          else
1291            sprintf(dataInputsKVP,"%s@%s=%s",itemp,icurs->name,url_encode(icurs->value));
1292          free(itemp);
1293        }
1294        icurs=icurs->next;
1295      }
1296    }
1297    curs=curs->next;
1298    i++;
1299  }
1300  return dataInputsKVP;
1301}
1302#endif
Note: See TracBrowser for help on using the repository browser.

Search

ZOO Sponsors

http://www.zoo-project.org/trac/chrome/site/img/geolabs-logo.pnghttp://www.zoo-project.org/trac/chrome/site/img/neogeo-logo.png http://www.zoo-project.org/trac/chrome/site/img/apptech-logo.png http://www.zoo-project.org/trac/chrome/site/img/3liz-logo.png http://www.zoo-project.org/trac/chrome/site/img/gateway-logo.png

Become a sponsor !

Knowledge partners

http://www.zoo-project.org/trac/chrome/site/img/ocu-logo.png http://www.zoo-project.org/trac/chrome/site/img/gucas-logo.png http://www.zoo-project.org/trac/chrome/site/img/polimi-logo.png http://www.zoo-project.org/trac/chrome/site/img/fem-logo.png http://www.zoo-project.org/trac/chrome/site/img/supsi-logo.png http://www.zoo-project.org/trac/chrome/site/img/cumtb-logo.png

Become a knowledge partner

Related links

http://zoo-project.org/img/ogclogo.png http://zoo-project.org/img/osgeologo.png