DBA Data[Home] [Help]

PACKAGE BODY: APPS.JDR_MDS_INTERNAL

Source


1 PACKAGE BODY jdr_mds_internal AS
2 /* $Header: JDRMDINB.pls 120.3 2005/09/02 11:38:31 nigoel noship $ */
3   -----------------------------------------------------------------------------
4   ---------------------------- PRIVATE VARIABLES ------------------------------
5   -----------------------------------------------------------------------------
6 
7   -- This is used to verify that the repository API and JRAD java code are
8   -- compatible.  When the repository API is updated, this constant should
9   -- be modified to match the latest version of JRAD.  In addition, in the
10   -- JRAD java code, CompatibleVersions.MIN_REPOS_VERSION may also need to
11   -- be modified.
12   REPOS_VERSION    CONSTANT VARCHAR2(32) := '9.0.5.2.0_459';
13 
14 
15   -- This is used to verify that the repository API and JRAD java code are
16   -- compatible.  This is the earliest JRAD version which is compatible
17   -- with the repository.
18   MIN_JRAD_VERSION CONSTANT VARCHAR2(32) := '9.0.3.5.0_226';
19 
20   -- NEWLINE character
21   NEWLINE CONSTANT VARCHAR2(1) := '
22 ';
23 
24   -- Maximimum size of XML chunk
25   MAX_CHUNK_SIZE CONSTANT INTEGER := 32000;
26 
27   -- Indentation for XML elements
28   INDENT_SIZE    CONSTANT INTEGER := 3;
29 
30   -- Maximum rows to fetch with bulk bind
31   ROWS_TO_FETCH  CONSTANT INTEGER := 1000;
32 
33   -- Exception raised when MAX_CHUNK_SIZE exceeded
34   chunk_size_exceeded EXCEPTION;
35   document_name_conflict EXCEPTION;
36   corrupt_sequence  EXCEPTION;
37 
38   -- Cursor to  retrieve all of the components of a document
39   CURSOR c_document_contents(docid NUMBER) IS
40     SELECT
41       comp_seq,
42       comp_element,
43       comp_level,
44       comp_grouping,
45       comp_id,
46       comp_ref,
47       comp_extends,
48       comp_use,
49       att_name,
50       att_value
51     FROM
52       jdr_components, jdr_attributes
53     WHERE
54       comp_docid = docid AND
55       comp_docid = att_comp_docid(+) AND
56       comp_seq = att_comp_seq(+)
57     ORDER BY
58       comp_seq,
59       att_comp_seq,
60       att_seq;
61 
62   -- Cursor to retrieve all of the components of a package
63   -- bug #(3785730) No need to use RULE hint as it does not help this query.
64   CURSOR c_package_contents(docid NUMBER) IS
65     SELECT
66       path_docid,
67       path_name,
68       path_type,
69       path_seq,
70       path_owner_docid,
71       comp_seq,
72       comp_element,
73       comp_level,
74       comp_grouping,
75       comp_id,
76       comp_ref,
77       comp_extends,
78       comp_use,
79       att_name,
80       att_value
81     FROM
82       jdr_paths, jdr_components, jdr_attributes
83     WHERE
84       path_docid IN
85         (SELECT
86            path_docid
87          FROM
88            jdr_paths
89          START WITH
90            path_docid = docID
91          CONNECT BY PRIOR
92            path_docid=path_owner_docid
93         ) AND
94       path_docid = comp_docid(+) AND
95       comp_docid = att_comp_docid(+) AND
96       comp_seq = att_comp_seq(+)
97     ORDER BY
98       path_seq,
99       comp_seq,
100       att_comp_seq,
101       att_seq;
102 
103    -- Cursor to retrieve XLIFF translations
104    -- This query will retrieve the translations for a specified
105    -- document (as well as sub-documents if it's a package file) and
106    -- specified language; and will retrieve the base language as well.
107    CURSOR c_trans(docid jdr_paths.path_docid%TYPE, lang VARCHAR2) IS
108     SELECT
109       atl_comp_docid,
110       atl_comp_ref,
111       atl_name,
112       atl_value
113     FROM
114       jdr_attributes_trans
115     WHERE
116       atl_comp_docid IN (SELECT path_docid FROM jdr_paths
117                          START WITH path_docid = docID
118                          CONNECT BY PRIOR path_docid=path_owner_docid) AND
119       atl_lang = lang
120     ORDER BY
121       atl_comp_docid,
122       atl_comp_ref;
123 
124 
125   -- State needed for exportXML
126   TYPE CharArray IS VARRAY(100) OF VARCHAR2(128);
127   TYPE NumArray IS VARRAY(100) OF NUMBER;
128 
129  -- Types needed for bulk bind
130   TYPE pathdocidtab IS TABLE OF jdr_paths.path_docid%TYPE;
131   TYPE pathnametab IS TABLE OF jdr_paths.path_name%TYPE;
132   TYPE pathtypetab IS TABLE OF jdr_paths.path_type%TYPE;
133   TYPE pathseqtab IS TABLE OF jdr_paths.path_seq%TYPE;
134   TYPE pathownertab IS TABLE OF jdr_paths.path_owner_docid%TYPE;
135   TYPE compseqtab IS TABLE OF jdr_components.comp_seq%TYPE;
136   TYPE compelementtab IS TABLE OF jdr_components.comp_element%TYPE;
137   TYPE compleveltab IS TABLE OF jdr_components.comp_level%TYPE;
138   TYPE compgroupingtab IS TABLE OF jdr_components.comp_grouping%TYPE;
139   TYPE compidtab IS TABLE OF jdr_components.comp_id%TYPE;
140   TYPE compreftab IS TABLE OF jdr_components.comp_ref%TYPE;
141   TYPE compextendstab IS TABLE OF jdr_components.comp_extends%TYPE;
142   TYPE compusetab IS TABLE OF jdr_components.comp_use%TYPE;
143   TYPE attnametab IS TABLE OF jdr_attributes.att_name%TYPE;
144   TYPE attvaluetab IS TABLE OF jdr_attributes.att_value%TYPE;
145   TYPE varchar64tab IS TABLE OF VARCHAR2(64) INDEX BY BINARY_INTEGER;
146 
147   mStack         CharArray := NULL;
148   mPackageNames  CharArray := NULL;
149   mPackageStack  NumArray := NULL;
150   mPackageLevel  INTEGER;
151   mPreviousLevel jdr_components.comp_level%TYPE;
152   mPreviousComp  jdr_components.comp_seq%TYPE;
153   mPreviousDocID jdr_paths.path_docid%TYPE;
154   mPreviousType  jdr_paths.path_type%TYPE;
155   mPartialChunk  VARCHAR2(32000);
156   mPartialXLIFFChunk  VARCHAR2(32000);
157   mPathType      jdr_paths.path_type%TYPE;
158   mIndex         INTEGER;
159   mFormatted     INTEGER;
160   mFetchComplete BOOLEAN;
161   mPathIds       pathdocidtab;
162   mPathNames     pathnametab;
163   mPathTypes     pathtypetab;
164   mPathSeqs      pathseqtab;
165   mPathOwners    pathownertab;
166   mCompSeqs      compseqtab;
167   mCompElements  compelementtab;
168   mCompLevels    compleveltab;
169   mCompGroupings compgroupingtab;
170   mCompIds       compidtab;
171   mCompRefs      compreftab;
172   mCompExtends   compextendstab;
173   mCompUses      compusetab;
174   mAttNames      attnametab;
175   mAttValues     attvaluetab;
176 
177   -- #(3803543) This was added to reduce the number of SQL executions
178   -- needed to get the document id for a given document name
179   mPackageCache  varchar64tab;
180 
181   -- User-defined exceptions.
182   -- Each of the following errors should correspond to an error in the
183   -- oracle.jrad.repos.api.DBAccess class.
184   ERROR_BASE                    CONSTANT INTEGER := -20100;
185   ERROR_DOCUMENT_NAME_CONFLICT  CONSTANT INTEGER := ERROR_BASE;
186   ERROR_PACKAGE_NAME_CONFLICT   CONSTANT INTEGER := ERROR_BASE - 1;
187   ERROR_CORRUPT_SEQUENCE        CONSTANT INTEGER := ERROR_BASE - 2;
188   ERROR_INVALID_NAME            CONSTANT INTEGER := ERROR_BASE - 3;
189   ERROR_INCONSISTENT_MAPPING    CONSTANT INTEGER := ERROR_BASE - 4;
190   ERROR_ILLEGAL_MAPPING         CONSTANT INTEGER := ERROR_BASE - 5;
191 
192   -----------------------------------------------------------------------------
193   ----------------------------- PRIVATE FUNCTIONS -----------------------------
194   -----------------------------------------------------------------------------
195 
196   --
197   -- Creates the XML for the specified attribute
198   --
199   PROCEDURE addAttribute(
200     newxml IN OUT NOCOPY /* file.sql.39 change */ VARCHAR2,
201     name          VARCHAR2,
202     value         VARCHAR2)
203   IS
204   BEGIN
205     IF (name IS NOT NULL) THEN
206       newxml := newxml || ' ' || name || '="' || value || '"';
207     END IF;
208   END;
209 
210 
211   --
212   -- Creates the XML for the new component
213   --
214   PROCEDURE addComponent(
215     newxml IN OUT NOCOPY /* file.sql.39 change */   VARCHAR2,
216     compseq         jdr_components.comp_seq%TYPE,
217     compelement     jdr_components.comp_element%TYPE,
218     complevel       jdr_components.comp_level%TYPE,
219     compgrouping    jdr_components.comp_grouping%TYPE,
220     compid          jdr_components.comp_id%TYPE,
221     compref         jdr_components.comp_ref%TYPE,
222     compextends     jdr_components.comp_extends%TYPE,
223     compuse         jdr_components.comp_use%TYPE,
224     attname         jdr_attributes.att_name%TYPE,
225     attvalue        jdr_attributes.att_value%TYPE,
226     formatted       BOOLEAN DEFAULT TRUE)
227   IS
228     adjLevel     INTEGER;
229   BEGIN
230     --
231     -- If there is a grouping for this component, subtract one from the
232     -- level (as that's the actual starting level of the new component).
233     --
234     IF (compgrouping IS NOT NULL) THEN
235       adjLevel := complevel - 1;
236     ELSE
237       adjLevel := complevel;
238     END IF;
239 
240     -- End the previous component (assuming this is not the first component)
241     IF (complevel <> 0) THEN
242       IF (adjLevel <= mPreviousLevel) THEN
243         -- The previous component has no children, so we can end the tag now
244         newxml := newxml || '/>' || NEWLINE;
245       ELSE
246         -- There are potential children to come, so keep the tag open
247         newxml := newxml || '>' || NEWLINE;
248       END IF;
249     END IF;
250 
251     -- Check if we need to pop any components/groupings from the stack
252     FOR i IN REVERSE  adjLevel+1..mPreviousLevel LOOP
253       IF (formatted) THEN
254         newxml := newxml || rpad(' ', (mPackageLevel+i-1)*INDENT_SIZE, ' ');
255       END IF;
256       newxml := newxml ||  '</' || mStack(i) || '>' || NEWLINE;
257     END LOOP;
258 
259     -- Push the grouping (if it exists)
260     IF (compgrouping IS NOT NULL) THEN
261       mStack(complevel) := compgrouping;
262       IF (formatted) THEN
263         newxml := newxml ||
264                   rpad(' ', (mPackageLevel+complevel-1)*INDENT_SIZE, ' ');
265       END IF;
266       newxml := newxml || '<' || compgrouping || '>' || NEWLINE;
267     END IF;
268 
269     -- Add the element
270     mStack(complevel+1) := compelement;
271     IF (formatted) THEN
272       newxml := newxml ||
273                 rpad(' ', (mPackageLevel+complevel)*INDENT_SIZE, ' ');
274     END IF;
275     newxml := newxml || '<' || compelement;
276 
277     -- Add the flat attributes
278     IF (compid IS NOT NULL) THEN
279       newxml := newxml || ' id="' || compid || '"';
280     END IF;
281 
282     IF (compref IS NOT NULL) THEN
283       IF (compextends = 'Y') THEN
284         newxml :=  newxml || ' extends="' || compref || '"';
285        ELSE
286        newxml := newxml || ' ref="' || compref || '"';
287       END IF;
288     END IF;
289 
290     IF (compuse IS NOT NULL) THEN
291       newxml := newxml || ' use="' || compuse || '"';
292     END IF;
293 
294     -- Add the 4th-normal attribute (if any) from the attributes table
295     addAttribute(newxml, attname, attvalue);
296 
297     -- Update the state
298     mPreviousLevel := complevel;
299     mPreviousComp := compseq;
300   END;
301 
302 
303   --
304   -- Add the XML to the current chunk and raise an exception if the
305   -- maximum size is exceeded.
306   --
307   PROCEDURE addXMLtoChunk(
308     chunk  IN OUT NOCOPY /* file.sql.39 change */   VARCHAR2,
309     newxml          VARCHAR2)
310   IS
311   BEGIN
312     -- bug #(3955120) Use lengthB to correctly compute the size
313     -- when multibyte characters are used.
314     IF ((LENGTHB(newxml) + LENGTHB(chunk)) > MAX_CHUNK_SIZE) THEN
315       RAISE chunk_size_exceeded;
316     ELSE
317       chunk := chunk || newxml;
318     END IF;
319   END;
320 
321 
322   --
323   -- Create the XML header for the given document ID
324   --
325   FUNCTION addXMLHeader(
326     docID         JDR_PATHS.PATH_DOCID%TYPE) RETURN VARCHAR2
327   IS
328     xml_version   jdr_paths.path_xml_version%TYPE;
329     xml_encoding  jdr_paths.path_xml_encoding%TYPE;
330   BEGIN
331     -- Get the version and encoding
332     SELECT
333       path_xml_version, path_xml_encoding
334     INTO
335       xml_version, xml_encoding
336     FROM
337       jdr_paths
338     WHERE
339       path_docid = docID;
340 
341     -- ###
342     -- ### #(2424399) Need to be able to retrieve XML_ENCODING and XML_VERSION
343     -- ### from the XML file
344     -- ###
345     IF (xml_encoding IS NULL) THEN
346       xml_encoding := 'UTF-8';
347     END IF;
348     IF (xml_version IS NULL) THEN
349       xml_version := '1.0';
350     END IF;
351 
352     -- Return the xml header
356 
353     RETURN ('<?xml version=''' || xml_version || ''' encoding=''' ||
354             xml_encoding || '''?>' || NEWLINE);
355   END;
357 
358   --
359   -- Retrieves the document id for the specified fully qualified path name.
360   -- The pathname must begin with a '/' and should look something like:
361   --   /oracle/apps/AK/mydocument
362   --
363   -- Parameters:
364   --   fullPathName  - the fully qualified name of the document
365   --
366   --   allowChildDoc - a non-zero value indicates that "child" documents
367   --                   are allowed, where child documents are documents
368   --                   which exist as part of a package document
369   --
370   --   includePackage - a non-zero value indicates that, if this is a "child"
371   --                    document and it allowChildDoc is TRUE, then the docid
372   --                    of the package document will be returned; otherwise,
373   --                    the ID of the "child" document will be returned
374   --
375   -- Returns:
376   --   Returns the ID of the path or -1 if no such path exists
377   --
378   FUNCTION getDocumentID(
379     fullPathName   VARCHAR2,
380     allowChildDoc  BOOLEAN,
381     includePackage BOOLEAN) RETURN NUMBER
382   IS
383     packageName   VARCHAR2(512);
384     pName         JDR_PATHS.PATH_NAME%TYPE;
385     pType         JDR_PATHS.PATH_TYPE%TYPE;
386     pSeq          JDR_PATHS.PATH_SEQ%TYPE;
387     ownerID       JDR_PATHS.PATH_OWNER_DOCID%TYPE := 0;
388     docID         JDR_PATHS.PATH_DOCID%TYPE := -1;
389     lastSlashPos  BINARY_INTEGER;
390   BEGIN
391     packageName := fullPathName;
392 
393     -- #(3403125) Remove the trailing forward slash
394     IF (INSTR(packageName, '/', LENGTH(packageName)) > 0)  THEN
395       packageName := SUBSTR(packageName, 1, LENGTH(packageName) - 1);
396     END IF;
397 
398     -- Separate the leaf path name and package name
399     lastSlashPos := INSTR(packageName, '/', -1);
400     pName := SUBSTR(packageName, lastSlashPos + 1);
401     packageName := SUBSTR(packageName, 1, lastSlashPos - 1);
402 
403     -- Get the id for the package
404     ownerID := getDocumentID(packageName, 'PACKAGE');
405     IF ownerID = -1 THEN
406       RETURN (-1);
407     END IF;
408 
409     -- Now get the ID for the document
410     SELECT path_docid, path_type, path_seq
411     INTO docID, pType, pSeq
412     FROM jdr_paths
413     WHERE path_name = pName AND path_owner_docid = ownerID;
414 
415     IF (pType = 'PACKAGE') THEN
416       IF (pSeq = 0) THEN
417         -- This is a package document
418         RETURN (docID);
419       END IF;
420     ELSIF (pType = 'DOCUMENT') THEN
421       IF (pSeq = -1) THEN
422         -- This is a document which is not apart of a package document
423         RETURN (docID);
424       ELSIF (allowChildDoc) THEN
425         --
426         -- This is a child document, so we need to return either the
427         -- ID of the package document, or the ID of the child document.
428         --
429         IF (includePackage) THEN
430           RETURN (getPackageDocument(docID));
431         ELSE
432           RETURN (docID);
433         END IF;
434       END IF;
435     END IF;
436 
437     RETURN (-1);
438   EXCEPTION
439     WHEN NO_DATA_FOUND THEN
440       RETURN (-1);
441   END;
442 
443 
444   -----------------------------------------------------------------------------
445   ----------------------------- PUBLIC FUNCTIONS ------------------------------
446   -----------------------------------------------------------------------------
447 
448 
449   --
450   -- Creates an entry in the jdr_paths table for the document or package.
451   -- The full name of the document/package must be specified.  Any packages
452   -- which do not already exist will be created as well.
456   --   fullPathName - the complete path name of the document or package
453   --
454   -- Parameters:
455   --   username     - user who is creating the document
457   --   docType      - either 'PACKAGE' or 'DOCUMENT' OR NULL
458   --   xmlversion   - xml version
459   --   xmlencoding  - xml encoding
460   --
461   -- Returns:
462   --   the ID of the created path
463   --
464   FUNCTION createPath(
465     username     VARCHAR2,
466     fullPathName VARCHAR2,
467     docType      VARCHAR2,
468     xmlversion   VARCHAR2,
469     xmlencoding  VARCHAR2) RETURN NUMBER
470   IS
471     CURSOR c(packageName VARCHAR2, ownerID NUMBER) IS
472       SELECT path_docid
473       FROM jdr_paths
474       WHERE path_name = packageName AND
475             path_owner_docid = ownerID AND
476             path_seq = -1 AND
477             path_type = 'PACKAGE';
478 
479     ownerID     jdr_paths.path_docid%TYPE := 0;
480     newDocID    jdr_paths.path_docid%TYPE := 0;
481     docID       jdr_paths.path_docid%TYPE := 0;
482     pathSeq     jdr_paths.path_seq%TYPE := 0;
483     packageName jdr_paths.path_name%TYPE;
484     slashpos    INTEGER := 1;
485     tempdoc     VARCHAR2(1024) := fullPathName;
486   BEGIN
487 
488     -- Skip the first slash
489     IF (INSTR(tempdoc, '/') <> 1) THEN
490       RETURN (-1);
491     ELSE
492       tempdoc := SUBSTR(tempdoc, 2);
493     END IF;
494 
495     -- #(3403125) Remove the trailing forward slash
496     IF (INSTR(tempdoc, '/', LENGTH(tempdoc)) > 0)  THEN
497       tempdoc := SUBSTR(tempdoc, 1, LENGTH(tempdoc) - 1);
498     END IF;
499 
500     WHILE (slashpos <> 0) LOOP
501 
502       -- Search for the next slash
503       slashpos := INSTR(tempdoc, '/');
504 
505       -- A null docType indicates that this path represents a directory.
506       -- If so, do not create an entry for the package document or document.
507       IF ((slashpos = 0) AND (docType IS NOT NULL)) THEN
508         --
509         -- There are no more slashes, which means that all that is left
510         -- is the name of the package document (pathSeq = 0) or the name of
511         -- the document (pathSeq=-1).
512         --
513         IF (docType = 'PACKAGE') THEN
514           pathSeq := 0;
515         ELSE
516           pathSeq := -1;
517         END IF;
518         docID := createPath(username,
519                             tempdoc,
520                             ownerID,
521                             pathSeq,
522                             docType,
523                             xmlversion,
524                             xmlencoding);
525       ELSE
526         -- Get the package name
527         IF (slashpos <> 0) THEN
528           packageName := SUBSTR(tempdoc, 1, slashpos-1);
529           tempdoc := SUBSTR(tempdoc, slashpos+1);
530         ELSE
531           -- This will happen if this is the last part of the path and the
532           -- path represents a directory (i.e. docType is null)
533           packageName := tempdoc;
534         END IF;
535 
536         -- Does this package already exist
537         OPEN c(packageName, ownerID);
538         FETCH c INTO newDocID;
539 
540         -- Insert the package if it does not already exist
541         IF c%NOTFOUND THEN
542           newDocID := createPath(username, packageName, ownerID, -1, 'PACKAGE');
543         END IF;
544         CLOSE c;
545 
546         ownerID := newDocID;
547 
548         -- If this is the last part of the path (i.e. slashpos = 0), then
549         -- this path represents a directory and we are done
550         IF (slashpos = 0) THEN
551           docID := ownerID;
552         END IF;
553 
554       END IF;
555     END LOOP;
556 
557     RETURN (docID);
558   END;
559 
560 
561   --
562   -- Creates an entry in the jdr_paths document.
563   --
564   -- Parameters:
565   --   username     - user who is creating the document
566   --   pathname     - the name of the document/package (not fully qualified)
567   --   ownerID      - the ID of the owning package
568   --   pathSeq      - sequence of the path
569   --   docType      - either 'DOCUMENT' or 'PACKAGE'
570   --   xmlversion   - xml version, which can be null for "child" documents
571   --   xmlencoding  - xml encoding, which can be null for "child" documents
572 
573   --
574   -- Returns:
575   --   the ID of the created path
576   --
577   FUNCTION createPath(
578     username    VARCHAR2,
579     pathname    VARCHAR2,
580     ownerID     JDR_PATHS.PATH_OWNER_DOCID%TYPE,
581     pathSeq     JDR_PATHS.PATH_SEQ%TYPE,
582     docType     VARCHAR2,
583     xmlversion  VARCHAR2 DEFAULT NULL,
584     xmlencoding VARCHAR2 DEFAULT NULL) RETURN NUMBER
585   IS
586     docID      JDR_PATHS.PATH_DOCID%TYPE;
587   BEGIN
588     -- Get the next document ID
589     SELECT jdr_document_id_s.NEXTVAL INTO docID FROM DUAL;
590 
591     INSERT INTO jdr_paths
592       (PATH_NAME, PATH_DOCID, PATH_OWNER_DOCID, PATH_TYPE, PATH_SEQ,
593        PATH_XML_VERSION, PATH_XML_ENCODING,
594        CREATED_BY, CREATION_DATE, LAST_UPDATED_BY, LAST_UPDATE_DATE,
595        LAST_UPDATE_LOGIN)
596     VALUES
600 
597       (pathname, docID, ownerID, docType, pathSeq,
598        xmlversion, xmlencoding,
599        username, SYSDATE, username, SYSDATE, username);
601     RETURN (docID);
602   EXCEPTION
603     -- An exception can be caused by one of the following situations:
604     -- (1) If the sequence JDR_DOCUMENT_ID_S is corrupt (i.e. the current value
605     --     of the sequence is less than the maximum document ID)
606     -- (2) We are trying to insert a document whose name matches an existing
607     --     package or vice versa.  For example, suppose we have a document
608     --     called:
609     --       /demo/test/mydoc.xml
610     --     and we try to create a document called:
611     --       /demo/test.xml
612     --     This will fail due to the unique index on (path_owner_docid,
613     --     path_name).
614     --  (3) Two users are attempting to insert the same document at the same
615     --      time
616     WHEN DUP_VAL_ON_INDEX THEN
617       -- If this exception was caused by (3), then the following select should
618       -- now return the correct docid because the first user will have
619       -- finished saving the document.
620       DECLARE
621         cnt   INTEGER;
622       BEGIN
623         SELECT path_docid INTO docID
624         FROM jdr_paths
625         WHERE
626           path_name = pathname AND
627           path_owner_docid = ownerID AND
628           path_type = docType AND
629           path_seq = pathseq;
630         RETURN (docID);
631       EXCEPTION
632         -- Since no data was found, we know this was caused by either (1) or
633         -- or (2).  If the following query returns no rows, then this can only
634         -- be explained by a corrupt sequence; otherwise, we are dealing with
635         -- a name conflict.
636         WHEN NO_DATA_FOUND THEN
637           SELECT count(*) INTO cnt
638           FROM jdr_paths
639           WHERE
640             path_name = pathname AND
641             path_owner_docid = ownerID;
642 
643           IF (cnt = 0) THEN
644             raise corrupt_sequence;
645           ELSE
646             raise document_name_conflict;
647           END IF;
648       END;
649   END;
650 
651 
652   --
653   -- Delete the document.
654   --
655   -- Parameters:
656   --   docID  - ID of the document to delete
657   --   isDrop - should the document be dropped as well.  If TRUE, then the
658   --            document will be completely removed.  If FALSE, then only
659   --            the contents of the document will be deleted, and the entry
660   --            in the jdr_paths table will remain.
661   --
662   -- Notes:
663   --   If the document is a package document, then the "child" documents will
664   --   be deleted as well.
665   --
666   PROCEDURE deleteDocument(
667     docID      JDR_PATHS.PATH_DOCID%TYPE,
668     isDrop     BOOLEAN DEFAULT FALSE)
669   IS
670   BEGIN
671     -- Delete the attributes of the document
672     DELETE jdr_attributes WHERE att_comp_docid IN
673       (SELECT path_docid FROM jdr_paths
674        START WITH path_docid = docid
675        CONNECT BY PRIOR path_docid=path_owner_docid);
676 
677     -- Delete the components of the document
678     DELETE jdr_components WHERE comp_docid IN
679       (SELECT path_docid FROM jdr_paths
680        START WITH path_docid = docid
681        CONNECT BY PRIOR path_docid=path_owner_docid);
682 
683     --
684     -- If isDrop is TRUE, then delete the document from the jdr_paths
685     -- table.  Otherwise, mark the path_seq to a special value (-2).
686     --
687     -- We have to go through this complexity because:
688     -- (1) When updating a document, we want to make sure that the
689     --     document ID is preserved.  As such, we can not simply remove
690     --     the row from the jdr_paths table and create a new one later.
691     --
692     -- (2) However, there could be a scenario where a package document that
693     --     is originally in the package, is removed at some later time.  In
694     --     order to insure that these "dangling" packages get deleted
695     --     properly, we mark the path_seq to -2, and, if after saving the
696     --     document, the path_seq has not been changed to a postive integer,
697     --     then we know the document should be removed.
698     --
699     IF (isDrop) THEN
700       DELETE jdr_paths where path_docid IN
701         (SELECT path_docid FROM jdr_paths
702          START WITH path_docid = docid
703          CONNECT BY PRIOR path_docid=path_owner_docid);
704     ELSE
705         UPDATE jdr_paths SET path_seq = -2
706         WHERE path_seq > 0 AND path_docid IN
707          (SELECT path_docid FROM jdr_paths
708           START WITH path_docid = docid
709           CONNECT BY PRIOR path_docid=path_owner_docid);
710     END IF;
711 
712     -- ###
713     -- ### Do we need to deal with references, translations or customizations.
714     -- ###
715   END;
716 
717 
718   --
719   -- Drops the document and the document's contents from the repository.
720   -- If the document is a package document, the "child" documents of the
721   -- package document will be dropped as well.
722   --
723   PROCEDURE dropDocument(
724     docID      JDR_PATHS.PATH_DOCID%TYPE)
725   IS
726   BEGIN
727     -- ###
731   END;
728     -- ### Should we allow the dropping of child documents and/or packages?
729     -- ###
730     jdr_mds_internal.deleteDocument(docID, TRUE);
732 
733 
734   --
735   -- Export the XML for a "single" document and pass it back in 32k chunks.
736   -- This function will return XML chunks, with a maximum size of 32k.
737   --
738   -- A "single" document is simply a document which is not a package document.
739   -- See comments for exportDocumentAsXML for more information.
740   --
741   FUNCTION exportSingleDocument(
742     docID              JDR_PATHS.PATH_DOCID%TYPE,
743     iFormatted         INTEGER,
744     exportFinished OUT NOCOPY /* file.sql.39 change */ INTEGER)  RETURN VARCHAR2
745   IS
746     chunk         VARCHAR2(32000);
747     newxml        VARCHAR2(32000);
748     formatted     BOOLEAN := (iFormatted = 1);
749   BEGIN
750     -- Assume that the document will fit in this 32k chunk
751     exportFinished := 1;
752 
753     --
754     -- This procedure returns the XML for the specified document.  Since the
755     -- XML can be potentially large (greater than 32k) and since, for
756     -- performance reasons, we do not want to return more than 32k at a time,
757     -- this procedure may need to be called multiple times to retrieve the
758     -- entire document.  As such, the "state" of the export is stored in
759     -- package variables.
760     --
761     -- A non-null document indicates that the export process is just
762     -- being started, so let's do the necessary initialization.
763     --
764     IF (docID IS NOT NULL) THEN
765       -- Get the XML header
766       chunk := addXMLHeader(docID);
767 
768       -- Initialize the state of the export
769       mPackageLevel := 0;
770       mPreviousDocID := -1;
771       mPreviousComp := -1;
772       mPreviousLevel := -1;
773       mPreviousType := NULL;
774       mPartialChunk := '';
775       mIndex := -1;
776       mFetchComplete := FALSE;
777 
778       -- Verify that the cursor is closed
779       IF (c_document_contents%ISOPEN) THEN
780         -- ###
781         -- ### Not sure what to do here, as the cursor should never be open
782         -- ### For now, we'll just close it and cross our fingers
783         -- ###
784         CLOSE c_document_contents;
785       END IF;
786 
787       -- And open the cursor that will retrieve the documents/packages
788       OPEN c_document_contents(docID);
789 
790     ELSIF (mPartialChunk IS NULL) THEN
791       -- We have finished exporting the document, just return NULL to
792       -- indicate that there is no more XML for this document
793       RETURN (NULL);
794     ELSE
795       -- Get the leftovers (if any) from the previous call to this function
796       chunk := mPartialChunk;
797       mPartialChunk := '';
798     END IF;
799 
800     newxml := NULL;
801     IF (c_document_contents%ISOPEN) THEN
802       <<get_components_loop>>
803       LOOP
804         --
805         -- Retrieve the next set of rows if we are currently not in the
806         -- middle of processing a fetched set or rows.
807         --
808         IF (mIndex = -1) THEN
809           -- #(2995144) Check if there are any more rows to fetch
810           IF (mFetchComplete) THEN
811             CLOSE c_document_contents;
812             EXIT;
813           END IF;
814 
815           -- Fetch the next set of rows
816           FETCH c_document_contents BULK COLLECT
817           INTO mCompSeqs, mCompElements, mCompLevels, mCompGroupings,
818                mCompIds, mCompRefs, mCompExtends, mCompUses,
819                mAttNames, mAttValues
820           LIMIT ROWS_TO_FETCH;
821 
822           -- Since we are only fetching records if either (1) this is the first
823           -- fetch or (2) the previous fetch did not retrieve all of the
824           -- records, then at least one row should always be fetched.  But
825           -- checking just to make sure.
826           IF (c_document_contents%ROWCOUNT = 0) THEN
827             CLOSE c_document_contents;
828             EXIT;
829           END IF;
830 
831           -- Check if all of the rows have been fetched.  If so, indicate that
832           -- the fetch is complete so that another fetch is not made.
833           IF (c_document_contents%NOTFOUND) THEN
834             mFetchComplete := TRUE;
835           END IF;
836 
837           mIndex := mCompSeqs.FIRST;
838         END IF;
839 
840         <<add_components_loop>>
841         WHILE (mIndex <= mCompSeqs.COUNT) LOOP
842           IF (mCompSeqs(mIndex) <> mPreviousComp) THEN
843 
844             -- We are starting a new component, so add the "previous" component
845             addXMLtoChunk(chunk, newxml);
846             newxml := NULL;
847 
848             -- And start building the new component
849             addComponent(newxml,
850                          mCompSeqs(mIndex), mCompElements(mIndex),
851                          mCompLevels(mIndex), mCompGroupings(mIndex),
852                          mCompIds(mIndex), mCompRefs(mIndex),
853                          mCompExtends(mIndex), mCompUses(mIndex),
854                          mAttNames(mIndex), mAttValues(mIndex),
855                          formatted);
856           ELSE
857             --
858             -- This is the same sequence of the previous row, which means
862           END IF;
859             -- it's only a new attribute, so just add the XML for the attribute
860             --
861             addAttribute(newxml, mAttNames(mIndex), mAttValues(mIndex));
863 
864           -- Increment the index to get the next row
865           mIndex := mIndex + 1;
866         END LOOP add_components_loop;
867 
868         -- We are not in the middle of processing a bulk fetch anymore
869         mIndex := -1;
870 
871         -- Append any leftover XML
872         addXMLtoChunk(chunk, newxml);
873         newxml := NULL;
874 
875       END LOOP get_components_loop;
876 
877       --
878       -- We have finished exporting the document.  The only task that remains
879       -- it to end the previous component and to unwind the stack
880       --
881       newxml := NULL;
882 
883       -- End the previous element
884       newxml := newxml || '/>' || NEWLINE;
885 
886       -- Unwind the document stack
887       WHILE (mPreviousLevel > 0) LOOP
888         IF (formatted) THEN
889           newxml := newxml ||
890                     rpad(' ', (mPreviousLevel-1)*INDENT_SIZE, ' ');
891         END IF;
892         newxml := newxml || '</' || mStack(mPreviousLevel) || '>' || NEWLINE;
893         mPreviousLevel := mPreviousLevel - 1;
894       END LOOP;
895       addXMLtoChunk(chunk, newxml);
896     END IF;
897 
898     --
899     -- Return the current chunk, and set the mPartialChunk to NULL so that,
900     -- when entering this function again, we will know that we have finished
901     -- processing the document.
902     --
903     mPartialChunk := NULL;
904     RETURN (chunk);
905 
906   EXCEPTION
907     WHEN chunk_size_exceeded THEN
908       exportFinished := 0;
909       mPartialChunk := newxml;
910       RETURN (chunk);
911   END;
912 
913 
914   --
915   -- Export the XML for a package document and pass it back in 32k chunks.
916   -- This function will return XML chunks, with a maximum size of 32k.
917   --
918   -- See comments for exportDocumentAsXML for more information.
919   --
920   FUNCTION exportPackageDocument(
921     docID              JDR_PATHS.PATH_DOCID%TYPE,
922     iFormatted         INTEGER DEFAULT 1,
923     exportFinished OUT NOCOPY /* file.sql.39 change */ INTEGER)  RETURN VARCHAR2
924   IS
925     chunk         VARCHAR2(32000);
926     newxml        VARCHAR2(32000);
927     formatted     BOOLEAN := (iFormatted = 1);
928   BEGIN
929     -- Assume that the document will fit in this 32k chunk
930     exportFinished := 1;
931 
932     --
933     -- This procedure returns the XML for the specified document.  Since the
934     -- XML can be potentially large (greater than 32k) and since, for
935     -- performance reasons, we do not want to return more than 32k at a time,
936     -- this procedure may need to be called multiple times to retrieve the
937     -- entire document.  As such, the "state" of the export is stored in
938     -- package variables.
939     --
940     -- A non-null document indicates that the export process is just
941     -- being started, so let's do the necessary initialization.
942     --
943     IF (docID IS NOT NULL) THEN
944       chunk := addXMLHeader(docID);
945 
946       -- Initialize the state of the export
947       mPackageLevel := 0;
948       mPreviousDocID := -1;
949       mPreviousComp := -1;
950       mPreviousLevel := -1;
951       mPreviousType := NULL;
952       mPartialChunk := '';
953       mIndex := -1;
954       mFetchComplete := FALSE;
955 
956       -- Verify that the cursor is closed
957       IF (c_package_contents%ISOPEN) THEN
958         -- ###
959         -- ### Not sure what to do here, as the cursor should never be open
960         -- ### For now, we'll just close it and cross our fingers
961         -- ###
962         CLOSE c_package_contents;
963       END IF;
964 
965       -- And open the cursor that will retrieve contents of the package
966       OPEN c_package_contents(docID);
967 
968     ELSIF (mPartialChunk IS NULL) THEN
969       -- We have finished exporting the document, just return NULL to
970       -- indicate that there is no more XML for this document
971       RETURN (NULL);
972     ELSE
973       -- Get the leftovers (if any) from the previous call to this function
974       chunk := mPartialChunk;
975       mPartialChunk := '';
976     END IF;
977 
978     newxml := NULL;
979     IF (c_package_contents%ISOPEN) THEN
980       <<get_documents_loop>>
981       LOOP
982         --
983         -- Retrieve the next set of rows if we are currently not in the
984         -- middle of processing a fetched set or rows.
985         --
986         IF (mIndex = -1) THEN
987           -- #(2995144) Check if there are any more rows to fetch
988           IF (mFetchComplete) THEN
989             CLOSE c_package_contents;
990             EXIT;
991           END IF;
992 
993           -- Get the next set of rows
994           FETCH c_package_contents BULK COLLECT
995           INTO mPathIds, mPathNames, mPathTypes, mPathSeqs, mPathOwners,
996                mCompSeqs, mCompElements, mCompLevels, mCompGroupings,
997                mCompIds, mCompRefs, mCompExtends, mCompUses,
998                mAttNames, mAttValues
999           LIMIT ROWS_TO_FETCH;
1000 
1004           -- checking just to make sure.
1001           -- Since we are only fetching records if either (1) this is the first
1002           -- fetch or (2) the previous fetch did not retrieve all of the
1003           -- records, then at least one row should always be fetched.  But
1005           IF (c_package_contents%ROWCOUNT = 0) THEN
1006             CLOSE c_package_contents;
1007             EXIT;
1008           END IF;
1009 
1010           -- Check if all of the rows have been fetched.  If so, indicate that
1011           -- the fetch is complete so that another fetch is not made.
1012           IF (c_package_contents%NOTFOUND) THEN
1013             mFetchComplete := TRUE;
1014           END IF;
1015 
1016           mIndex := mPathIds.FIRST;
1017         END IF;
1018 
1019         <<get_documents_loop>>
1020         WHILE (mIndex <= mPathIds.COUNT) LOOP
1021           IF (mPathIds(mIndex) = mPreviousDocID) THEN
1022             IF (mCompSeqs(mIndex) = mPreviousComp) THEN
1023               --
1024               -- If this is the same sequence of the previous row, then it's
1025               -- only a new attribute, so just add the XML for the attribute
1026               --
1027               addAttribute(newxml, mAttNames(mIndex), mAttValues(mIndex));
1028             ELSE
1029               --
1030               -- This is a different sequence from the previous row, which means
1031               -- it's a different component, so add the XML for the new component
1032               --
1033               addComponent(newxml,
1034                            mCompSeqs(mIndex), mCompElements(mIndex),
1035                            mCompLevels(mIndex), mCompGroupings(mIndex),
1036                            mCompIds(mIndex), mCompRefs(mIndex),
1037                            mCompExtends(mIndex), mCompUses(mIndex),
1038                            mAttNames(mIndex), mAttValues(mIndex),
1039                            formatted);
1040             END IF;
1041           ELSE
1042             --
1043             -- This is a new document or package, which means we have to take
1044             -- care of the following actions:
1045             --
1046             -- (1) Finish the previous document or package
1047             -- (2) Unwind the package stack if necessary
1048             -- (3) Reset the state for a new document
1049             --
1050 
1051             -- End the previous element
1052             IF (mPreviousType = 'DOCUMENT') THEN
1053               newxml := newxml || '/>' || NEWLINE;
1054             ELSIF (mPreviousType = 'PACKAGE') THEN
1055               newxml := newxml || '>' || NEWLINE;
1056             END IF;
1057 
1058             -- Unwind the document stack
1059             WHILE (mPreviousLevel > 0) LOOP
1060               IF (formatted) THEN
1061                 newxml := newxml ||
1062                   rpad(' ', (mPackageLevel+mPreviousLevel-1)*INDENT_SIZE, ' ');
1063               END IF;
1064               newxml := newxml || '</' || mStack(mPreviousLevel) || '>' || NEWLINE;
1065               mPreviousLevel := mPreviousLevel - 1;
1066             END LOOP;
1067 
1068             -- Unwind the package stack
1069             IF (mPackageLevel > 0) THEN
1070               WHILE (mPathOwners(mIndex) <> mPackageStack(mPackageLevel)) LOOP
1071                 IF (formatted) THEN
1072                   newxml := newxml || rpad(' ', (mPackageLevel-1)*INDENT_SIZE, ' ');
1073                 END IF;
1074                 newxml := newxml || '</package>' || NEWLINE;
1075                 mPackageLevel := mPackageLevel - 1;
1076               END LOOP;
1077             END IF;
1078 
1079             -- Reset the state for this new document
1080             mPreviousDocID := mPathIds(mIndex);
1081             mPreviousComp := mCompSeqs(mIndex);
1082             mPreviousLevel := mCompLevels(mIndex);
1083             mPreviousType := mPathTypes(mIndex);
1084 
1085             IF (mPathTypes(mIndex) = 'DOCUMENT') THEN
1086               -- If it's a document, add the first component
1087               addComponent(newxml,
1088                            mCompSeqs(mIndex), mCompElements(mIndex),
1089                            mCompLevels(mIndex), mCompGroupings(mIndex),
1090                            mCompIds(mIndex), mCompRefs(mIndex),
1091                            mCompExtends(mIndex), mCompUses(mIndex),
1092                            mAttNames(mIndex), mAttValues(mIndex),
1093                            formatted);
1094             ELSIF (mPathTypes(mIndex) = 'PACKAGE') THEN
1095               IF (mPathSeqs(mIndex) = 0) THEN
1096                 --
1097                 -- This is the first package of a package file.  As such, it
1098                 -- does not have a packageName attribute.
1099                 --
1100                 newxml := newxml || '<' || mCompElements(mIndex);
1101               ELSE
1102                 IF (formatted) THEN
1103                   newxml := newxml || rpad(' ', (mPackageLevel)*INDENT_SIZE, ' ');
1104                 END IF;
1105                 newxml := newxml || '<' || mCompElements(mIndex) || ' packageName="' || mPathNames(mIndex) || '"';
1106               END IF;
1107 
1108               -- Add any attributes, although only the top-level package should
1109               -- have attributes.
1110               addAttribute(newxml, mAttNames(mIndex), mAttValues(mIndex));
1111 
1112               -- Add the package to the package stack
1113               mPackageLevel := mPackageLevel + 1;
1117           END IF;
1114               mPackageStack(mPackageLevel) := mPathIds(mIndex);
1115               mPackageNames(mPackageLevel) := mCompElements(mIndex);
1116             END IF;
1118 
1119           -- Increment the index to get the next row
1120           mIndex := mIndex + 1;
1121 
1122           -- Add the newxml to the chunk
1123           addXMLtoChunk(chunk, newxml);
1124           newxml := NULL;
1125 
1126         END LOOP add_documents_loop;
1127 
1128         -- We are not in the middle of processing a bulk fetch anymore
1129         mIndex := -1;
1130 
1131       END LOOP get_documents_loop;
1132 
1133       --
1134       -- We have finished exporting the document.  The only task that remains
1135       -- it to end the previous component and to unwind the document stack
1136       -- and package stack.
1137       --
1138       newxml := NULL;
1139 
1140       -- End the previous element
1141       IF (mPreviousType = 'DOCUMENT') THEN
1142         newxml := newxml || '/>' || NEWLINE;
1143       ELSIF (mPreviousType = 'PACKAGE') THEN
1144         newxml := newxml || '>' || NEWLINE;
1145       END IF;
1146 
1147       -- Unwind the document stack
1148       WHILE (mPreviousLevel > 0) LOOP
1149         IF (formatted) THEN
1150           newxml := newxml ||
1151             rpad(' ', (mPackageLevel+mPreviousLevel-1)*INDENT_SIZE, ' ');
1152         END IF;
1153         newxml := newxml || '</' || mStack(mPreviousLevel) || '>' || NEWLINE;
1154         mPreviousLevel := mPreviousLevel - 1;
1155       END LOOP;
1156 
1157       -- Unwind the package stack
1158       WHILE (mPackageLevel > 0) LOOP
1159         IF (formatted) THEN
1160            newxml := newxml || rpad(' ', (mPackageLevel-1)*INDENT_SIZE, ' ');
1161         END IF;
1162         newxml := newxml || '</' || mPackageNames(mPackageLevel) || '>' || NEWLINE;
1163         mPackageLevel := mPackageLevel - 1;
1164       END LOOP;
1165       addXMLtoChunk(chunk, newxml);
1166     END IF;
1167 
1168     --
1169     -- Return the current chunk, and set the mPartialChunk to NULL so that,
1170     -- when entering this function again, we will know that we have finished
1171     -- processing the document.
1172     --
1173     mPartialChunk := NULL;
1174     RETURN (chunk);
1175 
1176   EXCEPTION
1177     WHEN chunk_size_exceeded THEN
1178       exportFinished := 0;
1179       mPartialChunk := newxml;
1180       RETURN (chunk);
1181   END;
1182 
1183 
1184   --
1185   -- This method has been deprecated.  Please use the function which
1186   -- has a return value which indicates whether or not the export is
1187   -- complete.
1188   --
1189   -- Export the XML for a document and pass it back in 32k chunks.  This
1190   -- function will return XML chunks, with a maximum size of 32k.  When
1191   -- the entire document has been exported, this function will return NULL.
1192   --
1193   -- Specifying a document name will initiate the export.  Thereafter, a NULL
1194   -- document name should be passed in until the export is complete.
1195   -- That is, to export an entire document, you should do:
1196   --
1197   -- firstChunk := jdr_mds_internal.exportDocumentAsXML('/oracle/apps/fnd/mydoc');
1198   -- LOOP
1199   --   nextChunk := jdr_mds_internal.exportDocumentAsXML(NULL);
1200   --   EXIT WHEN nextChunk IS NULL;
1201   -- END LOOP;
1202   --
1203   -- Parameters:
1204   --   fullName  - the fully qualifued name of the document.  however,
1205   --               after the first chunk of text is exported, a NULL value
1206   --               should be passed in.
1207   --
1208   --   formatted- a non-zero value indicates that the XML is formatted nicely
1209   --               (i.e. whether or not the elements are indented)
1210   --
1211   --   allowChildDoc - a non-zero value indicates that "child" documents
1212   --                   can be exported, where child documents are documents
1213   --                   which exist as part of a package document
1214   --
1215   --   includePackage - a non-zero value indicates that, if this is a "child"
1216   --                    document and it allowChildDoc is TRUE, then the entire
1217   --                    package document should be exported; otherwise, only
1218   --                    the XML for the child document will be exported.
1219   --
1220   -- Returns:
1221   --   The exported XML, in 32k chunks.
1222   --
1223   FUNCTION exportDocumentAsXML(
1224     fullName           VARCHAR2,
1225     formatted          INTEGER DEFAULT 1,
1226     allowChildDoc      INTEGER DEFAULT 0,
1227     includePackage     INTEGER DEFAULT 0) RETURN VARCHAR2
1228   IS
1229     exportFinished     INTEGER;
1230   BEGIN
1231     return (exportDocumentAsXML(exportFinished,
1232                                 fullName,
1233                                 formatted,
1234                                 includePackage));
1235   END;
1236 
1237 
1238   --
1239   -- Export the XML for a document and pass it back in 32k chunks.  This
1240   -- function will return XML chunks, with a maximum size of 32k.
1241   --
1242   -- Specifying a document name will initiate the export.  Thereafter, a NULL
1243   -- document name should be passed in until the export is complete.
1244   -- That is, to export an entire document, you should do:
1245   --
1246   -- firstChunk := jdr_mds_internal.exportDocumentAsXML(isDone,
1250   -- END LOOP;
1247   --                                                    '/oracle/apps/fnd/mydoc');
1248   -- WHILE (isDone = 0)
1249   --   nextChunk := jdr_mds_internal.exportDocumentAsXML(isDone, NULL);
1251   --
1252   -- Parameters:
1253   --   exportFinished - OUT NOCOPY /* file.sql.39 change */ parameter which indicates whether or not the export
1254   --                    is complete.  1 indicates the entire document is
1255   --                    exported, 0 indicates that there are more chunks
1256   --                    remaining.
1257   --
1258   --   fullName  - the fully qualifued name of the document.  however,
1259   --               after the first chunk of text is exported, a NULL value
1260   --               should be passed in.
1261   --
1262   --   formatted - a non-zero value indicates that the XML is formatted nicely
1263   --               (i.e. whether or not the elements are indented)
1264   --
1265   --   allowChildDoc - a non-zero value indicates that "child" documents
1266   --                   can be exported, where child documents are documents
1267   --                   which exist as part of a package document
1268   --
1269   --   includePackage - a non-zero value indicates that, if this is a "child"
1270   --                    document and it allowChildDoc is TRUE, then the entire
1271   --                    package document should be exported; otherwise, only
1272   --                    the XML for the child document will be exported.
1273   --
1274   -- Returns:
1275   --   The exported XML, in 32k chunks.
1276   --
1277   FUNCTION exportDocumentAsXML(
1278     exportFinished OUT NOCOPY /* file.sql.39 change */ INTEGER,
1279     fullName           VARCHAR2,
1280     formatted          INTEGER DEFAULT 1,
1281     allowChildDoc      INTEGER DEFAULT 0,
1282     includePackage     INTEGER DEFAULT 0) RETURN VARCHAR2
1283   IS
1284     docID          jdr_paths.path_docid%TYPE;
1285   BEGIN
1286     --
1287     -- A non-null fullName indicates that this is the first time this function
1288     -- is being called for this document.  If so, we need to find the
1289     -- document ID and start the export process.
1290     --
1291     IF (fullName IS NOT NULL) THEN
1292       mFormatted := formatted;
1293 
1294       docID := getDocumentID(fullName, allowChildDoc = 1, includePackage = 1);
1295       IF (docID = -1) THEN
1296         -- Unable to find the document
1297         -- ###
1298         -- ### Give error if unable to locate document
1299         -- ###
1300         RETURN (NULL);
1301       END IF;
1302 
1303       -- Determine if we're exporting a document or a package.
1304       --
1305       -- #(2417655) Save the path type in a package variable, so if/when we
1306       -- re-enter this procedure, we will know whether we are exporting
1307       -- a package or a document.
1308       SELECT path_type INTO mPathType FROM jdr_paths WHERE path_docid = docid;
1309       IF (mPathType = 'PACKAGE') THEN
1310         RETURN (exportPackageDocument(docID, mFormatted, exportFinished));
1311       ELSE
1312         RETURN (exportSingleDocument(docID, mFormatted, exportFinished));
1313       END IF;
1314     ELSE
1315       IF (mPathType = 'PACKAGE') THEN
1316         RETURN (exportPackageDocument(null, mFormatted, exportFinished));
1317       ELSE
1318         RETURN (exportSingleDocument(null, mFormatted, exportFinished));
1319       END IF;
1320     END IF;
1321   END;
1322 
1323 
1324   --
1325   -- Export the translations in XLIFF format.  The document will be
1326   -- exported in 32k chunks.
1327   --
1328   FUNCTION exportXLIFFDocument(
1329     exportFinished  OUT NOCOPY /* file.sql.39 change */ INTEGER,
1330     document            VARCHAR2,
1331     language            VARCHAR2) RETURN VARCHAR2
1332   IS
1333     r_trans      c_trans%ROWTYPE;
1334     docID        jdr_paths.path_docid%TYPE;
1335     chunk        VARCHAR2(32000);
1336     newxml       VARCHAR2(32000);
1337     baseLanguage jdr_attributes.att_value%TYPE;
1338     compref      jdr_attributes_trans.atl_comp_ref%TYPE;
1339     source       jdr_attributes.att_value%TYPE;
1340     dotpos       INTEGER;
1341   BEGIN
1342       -- Assume that the document will fit in this 32k chunk
1343     exportFinished := 1;
1344 
1345     IF (document IS NOT NULL) THEN
1346       docID := getDocumentID(document, TRUE, FALSE);
1347       IF (docID = -1) THEN
1348         RETURN (NULL);
1349       END IF;
1350 
1351       -- Retrieve the base language.  This is the same query which is
1352       -- executed in DBAccess.getBaseDevelopmentLanguage
1353       BEGIN
1354         SELECT att_value INTO baseLanguage FROM jdr_attributes
1355         WHERE att_comp_docid = docID AND
1356               att_name = 'xml:lang' AND
1357               att_comp_seq = 0;
1358       EXCEPTION
1359         WHEN NO_DATA_FOUND THEN
1360           baseLanguage := 'Unknown';
1361       END;
1362 
1363       -- Create the XLIFF document
1364       chunk := '<?xml version = ''1.0'' encoding = ''UTF-8''?>' || NEWLINE ||
1365                '<!--DBDRV: -->' || NEWLINE ||
1366                '<xliff version="1.0">' || NEWLINE ||
1367                lpad(' ', INDENT_SIZE) ||  '<file datatype="jdr" original="' ||
1368                substr(document, instr(document, '/', -1) + 1) ||
1369                '" source-language="' || baseLanguage ||
1370                '" target-language="' || language || '">' || NEWLINE ||
1374     ELSE
1371                lpad(' ', INDENT_SIZE*2) || '<body>' || NEWLINE;
1372 
1373       OPEN c_trans(docID, language);
1375       chunk := mPartialXLIFFChunk;
1376     END IF;
1377 
1378     LOOP
1379       -- Get each tranlsation
1380       FETCH c_trans INTO r_trans;
1381       IF (c_trans%NOTFOUND) THEN
1382         CLOSE c_trans;
1383         EXIT;
1384       END IF;
1385 
1386       -- component ref which equals "." indicates a top-level component
1387       IF (r_trans.atl_comp_ref = '.') THEN
1388         compref := jdr_mds_internal.getDocumentName(r_trans.atl_comp_docid);
1389       ELSIF (INSTR(r_trans.atl_comp_ref, ':') <> 1) THEN
1390         compref := jdr_mds_internal.getDocumentName(r_trans.atl_comp_docid) ||
1391                    '..' || r_trans.atl_comp_ref;
1392       ELSE
1393         -- #(3260414) Views need to be handled specially.  If the component
1394         -- reference is something like: :reg.region2, then the reference
1395         -- should be: docname:reg..region2...id, not docname..:reg.region2...id
1396         dotpos := INSTR(r_trans.atl_comp_ref, '.');
1397         compref := jdr_mds_internal.getDocumentName(r_trans.atl_comp_docid);
1398         IF (dotpos > 0) THEN
1399           compref := compref ||
1400                      SUBSTR(r_trans.atl_comp_ref, 1, dotpos - 1) || '..' ||
1401                      SUBSTR(r_trans.atl_comp_ref, dotpos + 1);
1402         ELSE
1403           compref := compref || r_trans.atl_comp_ref;
1404         END IF;
1405       END IF;
1406 
1407       -- convert the /'s to .'s for XLIFF format
1408       compref := translate(compref, '/', '.');
1409 
1410       -- #(3477218) We need to be able to get the source for customization
1411       -- views.  Since this is more complicated non customization views, we
1412       -- are going to special case customization views.
1413       DECLARE
1414         viewID      VARCHAR2(255);
1415         viewCompRef VARCHAR2(255);
1416         startSeq    jdr_components.comp_seq%TYPE;
1417         endSeq      jdr_components.comp_seq%TYPE;
1418         startLevel  jdr_components.comp_level%TYPE;
1419       BEGIN
1420         IF (INSTR(r_trans.atl_comp_ref, ':') <> 1) THEN
1421           -- This is not a translation for a customization view.
1422           --
1423           -- #(3258371) Get the source for customization documents as well.
1424           SELECT
1425            source.att_value INTO source
1426           FROM
1427             jdr_components, jdr_attributes source, jdr_attributes custs
1428           WHERE
1429             source.att_comp_docid = r_trans.atl_comp_docid AND
1430             comp_docid = r_trans.atl_comp_docid AND
1431             custs.att_comp_docid = r_trans.atl_comp_docid AND
1432             source.att_name = r_trans.atl_name AND
1433             comp_seq = source.att_comp_seq AND
1434             comp_seq = custs.att_comp_seq AND
1435             (
1436               (
1437                 custs.att_name = r_trans.atl_name AND
1438                 (
1439                   comp_id = r_trans.atl_comp_ref OR
1440                   (comp_seq=0 AND r_trans.atl_comp_ref='.')
1441                 )
1442               )
1443               OR
1444               (
1445                 custs.att_name IN ('element') AND
1446                 custs.att_value = r_trans.atl_comp_ref AND
1450         ELSE
1447                 comp_element IN ('view', 'modify', 'move', 'insert')
1448               )
1449             );
1451           -- This is a translation for a customization view.  As such,
1452           -- we need to restrict the query to components contained within
1453           -- the customization view.
1454           IF (dotpos = 0) THEN
1455             viewID := SUBSTR(r_trans.atl_comp_ref, 2);
1456             viewCompRef := NULL;
1457           ELSE
1458             viewID := SUBSTR(r_trans.atl_comp_ref, 2, dotpos - 2);
1459             viewCompRef := SUBSTR(r_trans.atl_comp_ref, dotpos + 1);
1460           END IF;
1461 
1462           -- Get the starting sequence of the customization view
1463           SELECT comp_seq, comp_level INTO startSeq, startLevel
1464           FROM jdr_components
1465           WHERE comp_docid =  r_trans.atl_comp_docid AND
1466                 comp_element = 'view' AND
1467                 comp_id = viewID;
1468 
1469           -- and the ending sequence
1470           SELECT MAX(comp_seq) - 1 INTO endSeq
1471           FROM jdr_components
1472           WHERE comp_docid = r_trans.atl_comp_docid AND
1473                 comp_seq > startSeq AND
1474                 comp_level <= startLevel;
1475 
1476           SELECT
1477             DISTINCT source.att_value INTO source
1478           FROM
1479             jdr_components, jdr_attributes source, jdr_attributes custs
1480           WHERE
1481             source.att_comp_docid = r_trans.atl_comp_docid AND
1482             comp_docid = r_trans.atl_comp_docid AND
1483             custs.att_comp_docid = r_trans.atl_comp_docid AND
1484             source.att_name = r_trans.atl_name AND
1485             comp_seq = source.att_comp_seq AND
1486             comp_seq = custs.att_comp_seq AND
1487             comp_seq >= startSeq AND
1488             (comp_seq <= endSeq OR endSeq IS NULL) AND
1489             comp_element IN ('view', 'modify', 'move', 'insert') AND
1490             --
1491             -- Either the viewCompRef is not null, indicating that the reference
1492             -- is not a top level component, in which case there must be
1493             -- an element attribute matching the component reference; or the
1494             -- viewCompRef is null, indicating the reference is a top level
1495             -- component, in which case there must not be an element attribute.
1496             --
1497             (
1498               (
1499                 viewCompRef IS NOT NULL AND
1500                 custs.att_name IN ('element') AND
1501                 custs.att_value = viewCompRef
1502               ) OR
1503               (
1504                 viewCompRef IS NULL AND
1505                 comp_element IN ('view', 'modify') AND
1506                 NOT EXISTS (SELECT att_name FROM jdr_attributes
1507                             WHERE  att_comp_docid = r_trans.atl_comp_docid AND
1508                                    att_comp_seq = comp_seq AND
1509                                    att_name = 'element')
1510               )
1511             );
1512         END IF;
1513       EXCEPTION
1514         -- Set source to NULL it this is a dangling translation
1515         WHEN no_data_found THEN
1516           source := NULL;
1517       END;
1518 
1519       -- add the translation
1520       newxml := lpad(' ', INDENT_SIZE*3) ||
1521                 '<trans-unit id="' ||
1522                 compref || '...' || r_trans.atl_name || '" ' ||
1523                 'translate="yes">'  || NEWLINE ||
1524                 lpad(' ', INDENT_SIZE*4) || '<source>' ||
1525                 source || '</source>' || NEWLINE ||
1526                 lpad(' ', INDENT_SIZE*4) || '<target>' ||
1527                 r_trans.atl_value || '</target>' || NEWLINE ||
1528                 lpad(' ', INDENT_SIZE*3) || '</trans-unit>' || NEWLINE;
1529 
1530       addXMLtoChunk(chunk, newxml);
1531     END LOOP;
1532 
1533     -- We are done, just need to add the closing tags
1534     newxml := lpad(' ', INDENT_SIZE*2) || '</body>' || NEWLINE ||
1535               lpad(' ', INDENT_SIZE) || '</file>' || NEWLINE ||
1536               '</xliff>' || NEWLINE;
1537     addXMLtoChunk(chunk, newxml);
1538 
1539     mPartialXLIFFChunk := NULL;
1540     RETURN (chunk);
1541 
1542   EXCEPTION
1543     WHEN chunk_size_exceeded THEN
1544       exportFinished := 0;
1545       mPartialXLIFFChunk := newxml;
1546       RETURN (chunk);
1547   END;
1548 
1549 
1550   --
1551   -- Retrieves the document id for the specified fully qualified path name.
1552   -- The pathname must begin with a '/' and should look something like:
1553   --   /oracle/apps/AK/mydocument
1554   --
1555   -- Parameters:
1556   --   fullPathName  - the fully qualified name of the document
1557   --   pathType      - the type of the document, either 'DOCUMENT' or 'PACKAGE'
1558   --                   if no type is specified, and there happens to be a path
1559   --                   of both 'DOCUMENT' and 'PACKAGE' (which is unlikely),
1560   --                   then the id of the DOCUMENT will be returned
1561   --
1562   -- Returns:
1566     fullPathName VARCHAR2,
1563   --   Returns the ID of the path or -1 if no such path exists
1564   --
1565   FUNCTION getDocumentID(
1567     pathType     VARCHAR2 DEFAULT NULL) RETURN NUMBER
1568   IS
1569     fullPath      VARCHAR2(512);
1570     cacheName     VARCHAR2(64);
1571     pathLevel     INTEGER;
1572     endIdx        INTEGER := -1;
1573     pathNames     pathnametab := pathnametab();
1574     docID         JDR_PATHS.PATH_DOCID%TYPE := -1;
1575     ownerID       JDR_PATHS.PATH_OWNER_DOCID%TYPE := 0;
1576     pType         JDR_PATHS.PATH_TYPE%TYPE;
1577     cachePackage  BOOLEAN := TRUE;
1578   BEGIN
1579     -- #(3234805) If the document does not start with a forward slash,
1580     -- then it's an invalid document name
1581     IF (INSTR(fullPathName, '/') <> 1) THEN
1582       RETURN (-1);
1583     END IF;
1584 
1585     -- Check if this is the root package
1586     IF ((fullPathName = '/') AND ((pathType IS NULL) OR (pathType = 'PACKAGE'))) THEN
1587       RETURN (0);
1588     END IF;
1589 
1590     -- #(3403125) Remove the trailing and first slash
1591     fullPath := substr(fullPathName, instr(fullPathName, '/') + 1);
1592     IF (INSTR(fullPath, '/', LENGTH(fullPath)) > 0)  THEN
1593       fullPath := SUBSTR(fullPath, 1, LENGTH(fullPath) - 1);
1594     END IF;
1595 
1596     -- Break up the document name into the individual packages
1597     WHILE (endIdx <> 0) LOOP
1598       endIdx := INSTR(fullPath, '/');
1599       pathNames.extend;
1600       IF endIdx = 0 THEN
1601         -- This is the leaf path name
1602         pathNames(pathNames.COUNT) := fullPath;
1603       ELSE
1604         -- Get the next package and remove it from the full path name
1605         pathNames(pathNames.COUNT)   := substr(fullPath, 1, endIdx - 1);
1606         fullPath := substr(fullPath, endIdx + 1);
1607       END IF;
1608     END LOOP;
1609 
1610     -- #(3803543) Check if there is a name in the cache.  We do this to
1611     -- reduce the amount of SQL.  This is specific to apps and the only
1612     -- packages which are cached are:
1613     --   /oracle/apps/xxx and
1614     --   /oracle/apps/xxx/customizations
1615     -- For /oracle/apps/xxx, the key is 'xxx'.
1616     -- For /oracle/apps/xxx/customizations, the key is '/xxx'.
1617     -- bug #(4137848) Lookup cache only if path atleast 3 levels
1618     -- to Avoid Subscript beyond count error
1619     IF (pathNames.COUNT > 2) THEN
1620       -- If the path does not begin with /oracle/apps, then no need to check
1621       -- the cache.
1622       IF (pathNames(1) = 'oracle' and pathNames(2) = 'apps') THEN
1623         IF (pathNames.COUNT > 3) AND (pathNames(4) = 'customizations') THEN
1624           -- If this path is in the cache, then we no we will be able to skip
1625           -- the /oracle/apps/xxx/customizations.  As such, we set the path
1626           -- level to 5, which is the level after customizations.
1627           pathLevel := 5;
1628           cacheName := '/'||pathNames(3);
1629         ELSE
1630           -- If this path is in the cache, then we no we will be able to skip
1631           -- the /oracle/apps/xxx.  As such, we set the path level to 4, which
1632           -- is the level after 'xxx'.
1633           pathLevel := 4;
1634           cacheName := pathNames(3);
1635         END IF;
1636 
1637         -- Now that we know the key, search the cache
1638         docID := mPackageCache.FIRST;
1639         WHILE docID IS NOT NULL LOOP
1640           IF (cacheName = mPackageCache(docID)) THEN
1641             cachePackage := FALSE;
1642             ownerID := docID;
1643             docID := NULL;
1644           ELSE
1645             docID := mPackageCache.NEXT(docID);
1646           END IF;
1647         END LOOP;
1648       END IF;
1649     END IF;
1650 
1651     -- ownerID will be non-zero if part of the document is in the cache
1652     IF (ownerID = 0) THEN
1653       -- Since the document was not in the cache, we have to go through each
1654       -- of the packages
1655       pathLevel := 1;
1656     ELSIF (pathLevel > pathNames.COUNT) THEN
1657       RETURN (ownerID);
1658     END IF;
1659 
1660     -- Loop through the remaining packages, getting the child package, until
1661     -- we are at the leaf package
1662     LOOP
1663       SELECT path_docid, path_type
1664       INTO docID, pType
1665       FROM jdr_paths
1666       WHERE path_name = pathNames(pathLevel) AND path_owner_docid = ownerID;
1667 
1668       -- Check to see if the package should be cached
1669       IF (cachePackage) THEN
1670         IF (pathLevel = 1) THEN
1671           -- We only cache names beginning with /oracle/apps, so if the first
1672           -- name is not 'oracle', we should not cache the name
1673           IF (pathNames(1) <> 'oracle') THEN
1674             cachePackage := FALSE;
1675           END IF;
1676         ELSIF (pathLevel = 2) THEN
1677           -- We only cache names beginning with /oracle/apps, so if the second
1678           -- name is not 'apps', we should not cache the name
1679           IF (pathNames(2) <> 'apps') THEN
1680             cachePackage := FALSE;
1681           END IF;
1682         ELSIF (pathLevel = 3) THEN
1683           -- Since the first two packages are /oracle/apps and since this is
1684           -- the third package, we should cache this value.
1685           mPackageCache(docID) := pathNames(3);
1686         ELSIF (pathLevel = 4) THEN
1690           IF (pathNames(4) = 'customizations') THEN
1687           -- We only cache names beginning with /oracle/apps/xxx/customizations,
1688           -- so if the 4th name is not 'customizations', we should not cache
1689           -- the name
1691             -- This is a package of the form, /oracle/apps/xxx/customizations,
1692             -- so cache this as '/xxx'
1693             mPackageCache(docID) := '/'||pathNames(3);
1694           END IF;
1695           cachePackage := FALSE;
1696         ELSE
1697           -- We do not cache any packages more than 5 levels deep
1698           cachePackage := FALSE;
1699         END IF;
1700       END IF;
1701 
1702       -- Check if this is the leaf package
1703       IF (pathLevel = pathNames.COUNT) THEN
1704         IF (pathType IS NULL) OR (pathType = pType) THEN
1705           RETURN (docID);
1706         ELSE
1707           RETURN (-1);
1708         END IF;
1709       END IF;
1710 
1711       -- Get ready for the next package
1712       ownerID := docID;
1713       pathLevel := pathLevel + 1;
1714     END LOOP;
1715   EXCEPTION
1716     WHEN NO_DATA_FOUND THEN
1717       RETURN (-1);
1718   END;
1719 
1720 
1721   --
1722   -- Retrieves the document id for the specified attributes.  This is
1723   -- typically used when attempting to find the id of a path which is
1724   -- owned by a package document.
1725   --
1726   -- Note that this will return the docID which matches the specified
1727   -- name, ownerID and docType (not pathSeq).  For the pathSeq, it will
1728   -- update the database path_seq to the value specified in pathSeq.
1729   --
1730   -- Parameters:
1731   --   name          - the name of the document (not fully qualified)
1732   --   ownerID       - the ID of the owning package
1733   --   pathSeq       - the path sequence
1734   --   docType       - either 'DOCUMENT' or 'PACKAGE'
1735   --
1736   -- Returns:
1737   --   Returns the ID of the path or -1 if no such path exists
1738   --
1739   FUNCTION getDocumentID(
1740     name       VARCHAR2,
1741     ownerID    JDR_PATHS.PATH_OWNER_DOCID%TYPE,
1742     pathSeq    JDR_PATHS.PATH_SEQ%TYPE,
1743     docType    JDR_PATHS.PATH_TYPE%TYPE) RETURN NUMBER
1744   IS
1745     docid JDR_PATHS.PATH_OWNER_DOCID%TYPE;
1746     seq   JDR_PATHS.PATH_SEQ%TYPE;
1747   BEGIN
1748     -- Find the docid for the specified attributes
1749     SELECT  path_docid, path_seq INTO docid, seq
1750     FROM jdr_paths
1751     WHERE
1752       path_name = name AND
1753       path_owner_docid = ownerID AND
1754       path_type = docType;
1755 
1756     -- If the sequence differs, then update it to the specified sequence
1757     IF (seq <> pathSeq) THEN
1758       UPDATE jdr_paths SET path_seq = pathSeq
1759       WHERE
1760         path_docid = docid;
1761     END IF;
1762 
1763     RETURN (docid);
1764 
1765   EXCEPTION
1766     WHEN NO_DATA_FOUND THEN
1767       RETURN (-1);
1768   END;
1769 
1770 
1771   --
1772   -- For each document name, retrieve the corresponding document ID.
1773   -- The document ID for docs[i] is in docIDs[i].  If no documentID
1774   -- exists for a docs[i], then docIDs[i] = -1.
1775   --
1776   PROCEDURE getDocumentIDs(docs   IN  jdr_stringArray,
1777                            docIDs OUT NOCOPY /* file.sql.39 change */ jdr_numArray)
1778   IS
1779     i NUMBER;
1780     ids jdr_numArray := jdr_numArray(null);
1781   BEGIN
1782     FOR i IN 1..docs.COUNT LOOP
1783       IF (i <> 1)
1784       THEN
1785         ids.extend;
1786       END IF;
1787       ids(ids.COUNT) := getDocumentID(docs(i), 'DOCUMENT');
1788     END LOOP;
1789     docIDs := ids;
1790   END;
1791 
1792 
1793   --
1794   -- Given the document id, find the fully qualified document name
1795   --
1796   -- Parameters:
1797   --   docID   - the ID of the document
1798   --
1799   -- Returns:
1800   --   the fully qualified document name
1801   --
1802   FUNCTION getDocumentName(
1803     docid NUMBER) RETURN VARCHAR2
1804   IS
1805     pathNames pathnametab;
1806     name      VARCHAR2(512) := '';
1807     i         INTEGER;
1808   BEGIN
1809     SELECT path_name BULK COLLECT INTO pathNames
1810     FROM jdr_paths
1811     START WITH path_docid = docid
1812     CONNECT BY PRIOR path_owner_docid = path_docid
1813     ORDER BY LEVEL DESC;
1814 
1815     FOR i IN 1..pathNames.COUNT LOOP
1816       name := name || '/' || pathNames(i);
1817     END LOOP;
1818 
1819     RETURN (name);
1820   END;
1821 
1822 
1823   --
1824   -- Given the document id of a child document, find the id for the
1825   -- owning package document.
1826   --
1827   -- Parameters:
1828   --   docID  - the ID of the child document
1829   --
1830   -- Returns:
1831   --   Returns the ID of the package document or -1 if not found
1832   --
1833   FUNCTION getPackageDocument(
1834     docID NUMBER) RETURN NUMBER
1835   IS
1836     pathSeq   jdr_paths.path_seq%TYPE := 0;
1837     ownerID   jdr_paths.path_owner_docid%TYPE;
1838     newDocid  jdr_paths.path_docid%TYPE := docID;
1839   BEGIN
1840     LOOP
1841       -- Retrieve the parent of the previous document
1842       SELECT path_owner_docid, path_seq
1846       EXIT WHEN pathSeq <= 0;
1843       INTO ownerID, pathSeq
1844       FROM jdr_paths
1845       WHERE path_docid = newDocID;
1847       newDocID := ownerID;
1848     END LOOP;
1849 
1850     -- Makse sure that we have retrieved a package document
1851     IF (pathSeq = 0) THEN
1852       RETURN (newDocID);
1853     ELSE
1854       RETURN (-1);
1855     END IF;
1856   EXCEPTION
1857     WHEN NO_DATA_FOUND THEN
1858       RETURN (-1);
1859   END;
1860 
1861 
1862   --
1863   -- Gets the minimun version of JRAD with which the repository API is
1864   -- compatible.  That is, the actual JRAD version must be >= to the
1865   -- minimum version of JRAD in order for the repositroy API and java code to
1866   -- be compatible.
1867   --
1868   -- Returns:
1869   --   Returns the mimumum version of JRAD
1870   --
1871   FUNCTION getMinJRADVersion RETURN VARCHAR2
1872   IS
1873   BEGIN
1874     RETURN (MIN_JRAD_VERSION);
1875   END;
1876 
1877 
1878 
1879   --
1880   -- Gets the version of the repository API.  This API version must >= to the
1881   -- java constant CompatibleVersions.MIN_REPOS_VERSION.
1882   --
1883   -- Returns:
1884   --   Returns the version of the repository
1885   --
1886   FUNCTION getRepositoryVersion RETURN VARCHAR2
1887   IS
1888   BEGIN
1889     RETURN (REPOS_VERSION);
1890   END;
1891 
1892   --
1893   -- Lock the document, given a valid docID.  Before updating/saving a document,
1894   -- it needs to be locked to ensure that it is not updated simultaneously by
1895   -- multipleusers.  If the document is already locked, we will continue to
1896   -- attempt to lock the document for a user-specified number of seconds, or
1897   -- MAX_SECONDS_TO_WAIT_FOR_LOCK by default, before giving up.
1898   -- If, after that time, we have still not locked the document, a
1899   -- "RESOURCE_BUSY" exception will be raised.
1900   --
1901   -- Parameters:
1902   --   docID   - ID of the document to lock
1903   --   attempts      - number of seconds to wait for a lock
1904   --
1905   PROCEDURE lockDocument(
1906     docID          JDR_PATHS.PATH_DOCID%TYPE,
1907     attempts       INTEGER DEFAULT MAX_SECONDS_TO_WAIT_FOR_LOCK
1908     )
1909   IS
1910     tmpdocID      JDR_PATHS.PATH_DOCID%TYPE;
1911     cont          BOOLEAN := TRUE;
1912     num_attempts  INTEGER := attempts;
1913     RESOURCE_BUSY EXCEPTION;
1914     PRAGMA EXCEPTION_INIT(RESOURCE_BUSY, -54);
1915   BEGIN
1916     WHILE (cont) LOOP
1917       BEGIN
1918         SELECT path_docid INTO tmpdocID
1919         FROM jdr_paths
1920         WHERE path_docid = docID
1921         FOR UPDATE NOWAIT;
1922 
1923         cont := false;
1924       EXCEPTION
1925         WHEN RESOURCE_BUSY THEN
1926           num_attempts := num_attempts - 1;
1927           IF (num_attempts <= 0) THEN
1928             RAISE;
1929           END IF;
1930           SYS.DBMS_LOCK.SLEEP(1);
1931       END;
1932     END LOOP;
1933   END;
1934 
1935 
1936   --
1937   -- Performs all steps that are necessary before a top-levle document is
1938   -- saved/updated which includes:
1939   -- (1) If the document already exists, updates the "who" columns in
1940   --     JDR_PATHS and deletes the contents of the document
1941   -- (2) If the document does not exist yet, creates a new entry in the
1942   --     JDR_PATHS table
1943   --
1944   -- Parameters:
1945   --   username     - user who is updating/inserting the document
1946   --   fullPathName - fully qualified name of the document/package file
1947   --   pathType     - 'DOCUMENT' for single document or 'PACKAGE' for package
1948   --                  file
1949   --   xmlversion   - xml version
1950   --   xmlencoding  - xml encoding
1951 
1952   --
1953   -- Returns:
1954   --   Returns the ID of the document or -1 if an error occurred
1955   --
1956   FUNCTION prepareDocumentForInsert(
1957     username     VARCHAR2,
1958     fullPathName VARCHAR2,
1959     pathType     VARCHAR2,
1960     xmlversion   VARCHAR2,
1961     xmlencoding  VARCHAR2) RETURN NUMBER
1962   IS
1963     docID   jdr_paths.path_docid%TYPE;
1964     pathSeq jdr_paths.path_seq%TYPE;
1965   BEGIN
1966 
1967     -- Check if the document already exists
1968     docID := getDocumentID(fullPathName, pathType);
1969 
1970     IF (docID = -1) THEN
1971       BEGIN
1972         -- Document does not exist yet, so create it now
1973         docID := createPath(username,
1974                             fullPathName,
1975                             pathType,
1976                             xmlversion,
1977                             xmlencoding);
1978       EXCEPTION
1979         -- #(2669626) If the sequence, JDR_DOCUMENT_ID_S, is corrupt (which can
1980         -- happen if it gets reset), then createPath will raise a NO_DATA_FOUND
1981         -- exception.
1982         WHEN corrupt_sequence THEN
1983           raise_application_error(ERROR_CORRUPT_SEQUENCE, NULL);
1984 
1985         -- #(2456503) If we were unable to create the path, it is likely due
1986         -- to an already existing document or package
1987         WHEN document_name_conflict THEN
1988           IF (pathType = 'DOCUMENT') THEN
1992           END IF;
1989             raise_application_error(ERROR_DOCUMENT_NAME_CONFLICT, NULL);
1990           ELSE
1991             raise_application_error(ERROR_PACKAGE_NAME_CONFLICT, NULL);
1993       END;
1994     ELSE
1995       -- #(2456503) Make sure that the document/package file does not already
1996       -- conflict with a document/package which already exists.
1997       SELECT path_seq INTO pathSeq FROM jdr_paths WHERE path_docid = docID;
1998 
1999       -- If attempting to save a document, then the path which we retrieved
2000       -- must be a top-level document (i.e. pathSeq  = -1).  If pathSeq > 0,
2001       -- this means that there is already a document of the same name inside a
2002       -- package file; and if pathSeq = 0, this means that a package file of
2003       -- the same name already exists in the repository.
2004       IF ((pathType = 'DOCUMENT') AND (pathSeq <> -1)) THEN
2005         raise_application_error(ERROR_DOCUMENT_NAME_CONFLICT, NULL);
2006       END IF;
2007 
2008       -- If attempting to save a package file, then the path which we retrieved
2009       -- must be a package file.  If pathSeq = -1, then this means there is
2010       -- already a package (not a package file) of the same name; and
2011       -- if pathSeq > 0, this means there is already a package in a package file
2012       -- of the same name.
2013       IF ((pathType = 'PACKAGE') AND (pathSeq <> 0)) THEN
2014         raise_application_error(ERROR_PACKAGE_NAME_CONFLICT, NULL);
2015       END IF;
2016 
2017       -- Lock the document
2018       lockDocument(docID);
2019 
2020       -- Document already exists, so update the "who" columns
2021       UPDATE jdr_paths
2022       SET path_xml_version = xmlversion,
2023           path_xml_encoding = xmlencoding,
2024           last_updated_by = username,
2025           last_update_date = SYSDATE,
2026           last_update_login = username
2027       WHERE path_docid = docID;
2028 
2029       -- And delete the "old" contents of the document
2030       deleteDocument(docID, FALSE);
2031     END IF;
2032 
2033     RETURN (docID);
2034   END;
2035 
2036 
2037   --
2038   -- Performs all steps that are necessary before a package document is
2039   -- saved/updated which includes:
2040   -- (1) If the document already exists, updates the "who" columns in
2041   --     JDR_PATHS
2042   -- (2) If the document does not exist yet, creates a new entry in the
2043   --     JDR_PATHS table
2044   --
2045   -- Parameters:
2046   --   username     - user who is updating/inserting the document
2047   --   pathname     - name of the document/package
2048   --   ownerID      - ID of the owning package
2049   --   pathSeq      - path sequence
2050   --   pathType     - 'DOCUMENT' or 'PACKAGE'
2051   --
2052   -- Returns:
2053   --   Returns the ID of the document or -1 if an error occurred
2054   --
2055   FUNCTION prepareDocumentForInsert(
2056     username   VARCHAR2,
2057     pathname   VARCHAR2,
2058     ownerID    JDR_PATHS.PATH_OWNER_DOCID%TYPE,
2059     pathSeq    JDR_PATHS.PATH_SEQ%TYPE,
2060     pathType   JDR_PATHS.PATH_TYPE%TYPE) RETURN NUMBER
2061   IS
2062     docID  jdr_paths.path_docid%TYPE;
2063   BEGIN
2064 
2065     -- Check if the document already exists
2066     docID := getDocumentID(pathname, ownerID, pathSeq, pathType);
2067 
2068     IF (docID = -1) THEN
2069       -- Document does not exist yet, so create it now
2070       docID := createPath(username, pathname, ownerID, pathSeq, pathType);
2071     ELSE
2072       -- Document already exists, so update the "who" columns
2073       UPDATE jdr_paths
2074       SET last_updated_by = username,
2075           last_update_date = SYSDATE,
2076           last_update_login = username
2077       WHERE path_docid = docID;
2078     END IF;
2079 
2080     RETURN (docID);
2081   EXCEPTION
2082     WHEN corrupt_sequence THEN
2083       raise_application_error(ERROR_CORRUPT_SEQUENCE, NULL);
2084 
2085     WHEN document_name_conflict THEN
2086       raise_application_error(ERROR_PACKAGE_NAME_CONFLICT, NULL);
2087   END;
2088 
2089 
2090   FUNCTION refactor(
2091     p_oldName      VARCHAR2,
2092     p_newName      VARCHAR2,
2093     p_translations INTEGER DEFAULT 1) RETURN INTEGER
2094   IS
2095     -- For each customized document which customizes the specified base
2096     -- document, retrieves the attributes for the specified component ID.
2097     CURSOR c_attributes(fullName VARCHAR2, baseName VARCHAR2, compID VARCHAR2) IS
2098       SELECT att_comp_docid,
2099              att_comp_seq,
2100              att_seq,
2101              att_name
2102       FROM (SELECT path_docid
2103             FROM jdr_attributes, jdr_paths
2104             WHERE path_docid = att_comp_docid AND
2105                   path_name = baseName AND
2106                   att_comp_seq = 0 AND
2107                   att_name = 'customizes' AND
2108                   att_value = fullName) docids,
2109            jdr_attributes, jdr_components
2110       WHERE att_comp_docid = docids.path_docid AND
2111             att_comp_docid = comp_docid AND
2112             att_comp_seq = comp_seq AND
2113             att_name IN ('element', 'before', 'after', 'parent') AND
2114             att_value = compID AND
2115             comp_element IN ('view', 'modify', 'move', 'insert', 'criterion');
2116 
2120       SELECT atl_comp_docid,
2117     -- For each customized document which customizes the specified base
2118     -- document, retrieves the translations for the specified component ID.
2119     CURSOR c_translations(fullName VARCHAR2, baseName VARCHAR2, compID VARCHAR2) IS
2121              atl_comp_ref
2122       FROM (SELECT path_docid
2123             FROM jdr_attributes, jdr_paths
2124             WHERE path_docid = att_comp_docid AND
2125                   path_name = baseName AND
2126                   att_comp_seq = 0 AND
2127                   att_name = 'customizes' AND
2128                   att_value = fullName) docids,
2129            jdr_attributes_trans
2130       WHERE atl_comp_docid = docids.path_docid AND
2131             (atl_comp_ref = compID OR atl_comp_ref like ':%.'||compID);
2132 
2133     -- Retrieve all of the customization documents which customize the
2134     -- specified base document.
2135     CURSOR c_documents(fullName VARCHAR2, baseName VARCHAR2) IS
2136       SELECT path_docid, created_by, jdr_mds_internal.getDocumentName(path_docid)
2137       FROM jdr_attributes, jdr_paths
2138       WHERE path_docid = att_comp_docid AND
2139             path_name = baseName AND
2140             att_comp_seq = 0 AND
2141             att_name = 'customizes' AND
2142             att_value = fullName;
2143 
2144     oldDocName     VARCHAR2(512);
2145     newDocName     VARCHAR2(512);
2146     oldCustDocName VARCHAR2(512);
2147     newCustDocName VARCHAR2(512);
2148     oldBaseName    jdr_paths.path_name%TYPE;
2149     newBaseName    jdr_paths.path_name%TYPE;
2150     oldCompName    jdr_components.comp_id%TYPE;
2151     newCompName    jdr_components.comp_id%TYPE;
2152     docID          jdr_paths.path_docid%TYPE;
2153     ownerID        jdr_paths.path_docid%TYPE;
2154     attCompSeq     jdr_attributes.att_comp_seq%TYPE;
2155     attSeq         jdr_attributes.att_seq%TYPE;
2156     attName        jdr_attributes.att_name%TYPE;
2157     oldCompRef     jdr_attributes_trans.atl_comp_ref%TYPE;
2158     newCompRef     jdr_attributes_trans.atl_comp_ref%TYPE;
2159     username       jdr_paths.created_by%TYPE;
2160     oldPeriodPos   INTEGER;
2161     newPeriodPos   INTEGER;
2162     slashPos       INTEGER;
2163     custPos        INTEGER;
2164     pos1           INTEGER;
2165     pos2           INTEGER;
2166     changesMade    INTEGER := 0;
2167   BEGIN
2168     -- Perform some simple check on the document name
2169     IF ( (INSTR(p_oldName, '/') <> 1) OR (INSTR(p_newName, '/') <> 1) ) THEN
2170       raise_application_error(ERROR_INVALID_NAME, NULL);
2171     END IF;
2172 
2173     -- If the names contain a ".", it means that we are refactoring
2174     -- a component.  Otherwise, we are refactoring a document
2175     oldPeriodPos := INSTR(p_oldName, '.');
2176     newPeriodPos := INSTR(p_newName, '.');
2177 
2178     -- Make sure we are not trying to map a component to a document or
2179     -- vice versa
2180     IF ( ((oldPeriodPos = 0) AND (newPeriodPos > 0)) OR
2181          ((oldPeriodPos > 0) AND (newPeriodPos = 0)) ) THEN
2182       raise_application_error(ERROR_INCONSISTENT_MAPPING, NULL);
2183     END IF;
2184 
2185     IF (oldPeriodPos > 0) THEN
2186       -- Break up the old component name into the component ID, base name and
2187       -- document name
2188       slashPos := INSTR(p_oldName, '/', -1);
2189       oldCompName := SUBSTR(p_oldName, oldPeriodPos + 1);
2190       oldBaseName := SUBSTR(p_oldName, slashPos + 1, oldPeriodPos - slashPos - 1);
2191       oldDocName := SUBSTR(p_oldName, 1, oldPeriodPos - 1);
2192 
2193       -- Break up the new compoent name into the component ID, base name and
2194       -- document name
2195       slashPos := INSTR(p_newName, '/', -1);
2196       newCompName := SUBSTR(p_newName, newPeriodPos + 1);
2197       newBaseName := SUBSTR(p_newName, slashPos + 1, newPeriodPos - slashPos - 1);
2198       newDocName := SUBSTR(p_newName, 1, newPeriodPos - 1);
2199 
2200       -- We do not support both renaming a component and renaming a document
2201       -- with the same mapping, so throw an error if this is the case
2202       IF (oldDocName <> newDocName) THEN
2203         raise_application_error(ERROR_ILLEGAL_MAPPING, NULL);
2204       END IF;
2205 
2206       -- Make any necessary changes to the customization documents,
2207       -- replacing the old component name eith the new component name
2208       OPEN c_attributes(oldDocName, oldBaseName, oldCompName);
2209       LOOP
2210         FETCH c_attributes INTO docID, attCompSeq, attSeq, attName;
2211         IF (c_attributes%NOTFOUND) THEN
2212           CLOSE c_attributes;
2213           EXIT;
2214         END IF;
2215 
2216         UPDATE jdr_attributes SET att_value = newCompName
2217         WHERE att_comp_docid = docID AND
2218               att_comp_seq = attCompSeq AND
2219               att_seq = attSeq AND
2220               att_name = attName;
2221 
2222         changesMade := changesMade + 1;
2223       END LOOP;
2224 
2225       -- Make any changes to translations, replacing the old component name
2226       -- with the new component name.
2227       IF (p_translations <> 0) THEN
2228         OPEN c_translations(oldDocName, oldBaseName, oldCompName);
2229         LOOP
2230           FETCH c_translations INTO docID, oldCompRef;
2231           IF (c_translations%NOTFOUND) THEN
2232             CLOSE c_translations;
2233             EXIT;
2237           --  :theview.componentID
2234           END IF;
2235 
2236           -- For customization views, the reference is of the form:
2238           IF (INSTR(oldCompRef, ':') = 1) THEN
2239             newCompRef := SUBSTR(oldCompRef, 1, INSTR(oldCompRef, '.')) || newCompName;
2240           ELSE
2241             newCompRef := newCompName;
2242           END IF;
2243 
2244           UPDATE jdr_attributes_trans SET atl_comp_ref = newCompRef
2245           WHERE atl_comp_docid = docID AND
2246                 atl_comp_ref = oldCompRef;
2247 
2248           changesMade := changesMade + 1;
2249         END LOOP;
2250       END IF;
2251     ELSE
2252       -- Since no component ID was specified, it means that we are dealing
2253       -- with the refactoring of a document, as opposed to a component
2254 
2255       -- Get the new base name and document name
2256       newDocName := p_newName;
2257       oldDocName := p_oldName;
2258       oldBaseName := SUBSTR(p_oldName, INSTR(p_oldName, '/', -1) + 1);
2259       newBaseName := SUBSTR(p_newName, INSTR(p_newName, '/', -1) + 1);
2260 
2261       -- Get each of the customization documents
2262       OPEN c_documents(oldDocName, oldBaseName);
2263       LOOP
2264         FETCH c_documents INTO docID, username, oldCustDocName;
2265         IF (c_documents%NOTFOUND) THEN
2266           CLOSE c_documents;
2267           EXIT;
2268         END IF;
2269 
2270         -- For each customization document, we need to rename the customization
2271         -- document and change the customizes attributes.  To rename the
2272         -- customization document, we first need to determine the new name of
2273         -- the customization document.
2274         --
2275         -- #(3455760) Originally, we always used the new style naming for the
2276         -- customization documents.  That is, we would convert old style
2277         -- customizations to the new naming style.  However, for consistency
2278         -- reasons, it was decided that it makes more sense to convert old
2279         -- style to old style and new style to new style.
2280         --
2281         -- For new style customizations, suppose the customization
2282         -- document is:
2283         --   /oracle/apps/jrad/webui/customizations/site/tower/musicpage
2284         -- And the new base document is:
2285         --   /oracle/apps/newjrad/webui/musicpage
2286         -- Then the new customization document is:
2287         --   /oracle/apps/newjrad/webui/customizations/site/tower/musicpage
2288         -- which is combination of:
2289         -- (1) the path name of the new document +
2290         -- (2) "customizations" +
2291         -- (3) layer type and layer value of the customization document +
2292         -- (4) the base name of the new document
2293         --
2294         -- For old style customizations, suppose the customization document
2295         -- is:
2296         --   /oracle/apps/jrad/customizations/site/tower/webui/musicpage
2297         -- And the new base document is:
2298         --   /oracle/apps/newjrad/webui/musicpage
2299         -- Then the new customization document is:
2300         --   /oracle/apps/newjrad/customizations/site/tower/webui/musicpage
2301         -- which is a combination of:
2302         -- (1) The first three packages of the new document +
2303         -- (2) "customizations" +
2304         -- (3) layer type and layer value of the customization document +
2305         -- (4) the remaining portion of the new document (minus the first three)
2306         --
2307         -- To determine whether or not this is an old style document, we
2308         -- simply need to count the slashes after the customizes portion.  It's
2309         -- an old style customizations if there are more than 3 slashes in the
2310         -- customizes portions, since new style customizations will have exactly
2311         -- 3 slashes (one for the layer type, one for the layer value and one
2312         -- for the document name).
2313         custPos := INSTR(oldCustDocName, '/customizations');
2314         IF (INSTR(oldCustDocName, '/', custPos + 1, 4) <> 0) THEN
2318           -- and that the new document name is:
2315           -- This is an old style customization.  For the following, suppose
2316           -- that the old customization document is :
2317           --   /oracle/apps/jrad/customizations/site/tower/webui/musicpage
2319           --   /oracle/apps/newjrad/webui/musicpage
2320           --
2321           -- (1) Get the first 3 packages of the new name.
2322           --     /oracle/apps/newjrad/
2323           newCustDocName := SUBSTR(newDocName, 1, INSTR(newDocName, '/', 1, 4));
2324 
2325           -- (2) Add the customizations portion
2326           --     /oracle/apps/newjrad/customizations
2327           newCustDocName := newCustDocName || 'customizations';
2328 
2329           -- (3) Add the layer type and layer value
2330           --     /oracle/apps/newjrad/customizations/site/tower/
2331           pos1 := INSTR(oldCustDocName, '/', custpos + 1, 1);
2332           pos2 := INSTR(oldCustDocName, '/', custpos + 1, 3);
2333           newCustDocName := newCustDocName ||
2334                             SUBSTR(oldCustDocName, pos1, pos2 - pos1 + 1);
2335 
2336           -- (4) Add the remaining portion of the new document
2337           --     /oracle/apps/newjrad/customizations/site/tower/webui/musicpage
2338           newCustDocName := newCustDocName ||
2339                             SUBSTR(newDocName, INSTR(newDocName, '/', 1, 4) + 1);
2340         ELSE
2341           -- This is an new style customization.  For the following, suppose
2342           -- that the old customization document is :
2343           --   /oracle/apps/jrad/webui/customizations/site/tower/musicpage
2344           -- and that the new document name is:
2345           --   /oracle/apps/newjrad/webui/musicpage
2346           --
2347           -- (1) Get the first path name of the new document
2348           --     /oracle/apps/newjrad/webui/
2349           newCustDocName := SUBSTR(newDocName, 1, INSTR(newDocName, '/', -1));
2350 
2351           -- (2) Add the customizations portion
2352           --     /oracle/apps/newjrad/webui/customizations
2353           newCustDocName := newCustDocName || 'customizations';
2354 
2355           -- (3) Add the layer type and layer value
2356           --     /oracle/apps/newjrad/webui/customizations/site/tower/
2357           pos1 := INSTR(oldCustDocName, '/', custpos + 1, 1);
2358           pos2 := INSTR(oldCustDocName, '/', custpos + 1, 3);
2359           newCustDocName := newCustDocName ||
2360                             SUBSTR(oldCustDocName, pos1, pos2 - pos1 + 1);
2361 
2362           -- (4) Add base name of the new document
2363           --     /oracle/apps/newjrad/webui/customizations/site/tower/musicpage
2364           newCustDocName := newCustDocName || newBaseName;
2365         END IF;
2366 
2367         -- Create the path for the new customized document.  If the path
2368         -- already exists, it will simply return the owner.
2369         ownerID := createPath(username,
2370                               RTRIM(newCustDocName, newBaseName), '', '', '');
2371         IF (ownerID = -1) THEN
2372           raise_application_error(ERROR_INVALID_NAME, NULL);
2373         END IF;
2374 
2375         -- Update the path with the new base name and new owner ID
2376         BEGIN
2377           UPDATE jdr_paths
2378           SET path_name = newBaseName,
2379               path_owner_docid = ownerID
2380           WHERE path_docid = docID;
2381         EXCEPTION
2382           -- This can happen if the customization already exists on the
2383           -- refactored base document.  If so, do not attempt to overwrite
2384           -- the existing customization.
2385           WHEN DUP_VAL_ON_INDEX THEN
2386             GOTO end_loop;
2387         END;
2388 
2389         -- Make the necessary changes to the customization documents, replacing
2390         -- the customizes attribute with the new base document name.
2391         UPDATE jdr_attributes
2392         SET att_value = newDocName
2393         WHERE att_comp_docid = docID AND
2394               att_comp_seq = 0 AND
2395               att_name = 'customizes' AND
2396               att_value = oldDocName;
2397 
2398         -- #(3456035) Modify the package attribute if the document has been
2399         -- moved to a new package
2400         IF (RTRIM(p_oldName, oldBaseName) <> RTRIM(p_newName, newBaseName)) THEN
2401           UPDATE jdr_attributes
2402           SET att_value = REPLACE(newCustDocName, '/'||newBaseName)
2403           WHERE att_comp_docid = docID AND
2404                 att_comp_seq = 0 AND
2405                 att_name = 'package';
2406         END IF;
2407 
2408         changesMade := changesMade + 1;
2409 
2410         <<end_loop>>
2411         NULL;
2412       END LOOP;
2413     END IF;
2414 
2415     RETURN (changesMade);
2416   END;
2417 
2418   --
2419   -- Remove any dangling references of the document.  This is only necessary
2420   -- for package documents.  Here's a scenario where this is relevant.
2421   -- Suppose you have a package document FOO which has child documents A, B and
2422   -- C.  Now suppose you are updating FOO, this time removing document C.
2423   -- In order make sure that all references to C are destroyed, this needs to
2424   -- be called after saving FOO.
2425   --
2426   PROCEDURE removeDanglingReferences(
2427     docID      JDR_PATHS.PATH_DOCID%TYPE)
2428   IS
2429   BEGIN
2430     DELETE jdr_paths
2431     WHERE path_seq=-2 AND path_docid IN
2432       (SELECT path_docid FROM jdr_paths
2433        START WITH path_docid = docid
2434        CONNECT BY PRIOR path_docid=path_owner_docid);
2435   END;
2436 
2437 
2438 BEGIN
2439   -- Initialize the stack for exporting XML
2440   mPackageStack := NumArray(NULL);
2441   mPackageStack.EXTEND(mPackageStack.LIMIT-1,1);
2442 
2443   mPackageNames := CharArray(NULL);
2444   mPackageNames.EXTEND(mPackageNames.LIMIT-1,1);
2445 
2449 END;
2446   mStack := CharArray(NULL);
2447   mStack.EXTEND(mStack.LIMIT-1,1);
2448