001package org.kuali.ole.docstore.repository;
002
003import org.apache.commons.io.FileUtils;
004import org.kuali.ole.docstore.OleDocStoreException;
005import org.kuali.ole.docstore.model.enums.DocFormat;
006import org.kuali.ole.docstore.model.xmlpojo.ingest.AdditionalAttributes;
007import org.kuali.ole.docstore.model.xmlpojo.ingest.RequestDocument;
008import org.kuali.ole.repository.NodeHandler;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012import javax.jcr.*;
013import java.io.*;
014import java.util.Calendar;
015import java.util.Collection;
016import java.util.Date;
017import java.util.Iterator;
018
019import static org.kuali.ole.docstore.process.ProcessParameters.*;
020
021/**
022 * Implements custom way of creating levels and nodes.
023 *
024 * @author tirumalesh.b
025 * @version %I%, %G%
026 *          Date: 28/8/12 Time: 6:09 PM
027 */
028public class CustomNodeManager
029        implements NodeManager {
030    private static final Logger logger = LoggerFactory.getLogger(NodeHandler.class);
031    private static CustomNodeManager ourInstance = new CustomNodeManager();
032
033    protected int numLevels = 3;
034
035    public static CustomNodeManager getInstance() {
036        return ourInstance;
037    }
038
039    public CustomNodeManager() {
040    }
041
042    public Node getParentNode(RequestDocument requestDocument, Session session) throws OleDocStoreException {
043        Node levelNode = null;
044        try {
045            Node formatNode = getStaticFormatNode(requestDocument, session);
046            synchronized (this.getClass()) {
047                for (int i = 1; i <= numLevels; i++) {
048                    levelNode = initLevelNode("l" + i, formatNode, false, session);
049                    formatNode = levelNode;
050                }
051                //                Node l1 = initLevelNode(NODE_LEVEL1, formatNode, false, session);
052                //                Node l2 = initLevelNode(NODE_LEVEL2, l1, false, session);
053                //                     l3 = initLevelNode(NODE_LEVEL3, l2, false, session);
054            }
055        } catch (Exception e) {
056            throw new OleDocStoreException(e);
057        }
058        return levelNode;
059    }
060
061
062    @Override
063    public void linkNodes(Node node, Node linkedDocumentNode, Session session) throws OleDocStoreException {
064    }
065
066    public Node getStaticFormatNode(RequestDocument doc, Session session) throws RepositoryException {
067        Node root = session.getRootNode();
068        Node categoryNode = initStaticNode(doc.getCategory(), root, session);
069        Node typeNode = initStaticNode(doc.getType(), categoryNode, session);
070        Node formatNode = initStaticNode(doc.getFormat(), typeNode, session);
071        return formatNode;
072    }
073
074    public Node initStaticNode(String nodeName, Node parentNode, Session session) throws RepositoryException {
075        Node node;
076        if (!parentNode.hasNode(nodeName)) {
077            synchronized (session) {
078                node = parentNode.addNode(nodeName, "nt:unstructured");
079                node.setProperty("nodeType", "folder");
080                node.addMixin("mix:referenceable");
081                session.save();
082            }
083        } else {
084            node = parentNode.getNode(nodeName);
085        }
086        return node;
087    }
088
089    public Node initNonStaticNode(String nodeName, Node parentNode) throws RepositoryException {
090        Node node;
091        node = parentNode.addNode(nodeName, "nt:unstructured");
092        node.setProperty("nodeType", "folder");
093        node.addMixin("mix:referenceable");
094        return node;
095    }
096
097    protected void modifyAdditionalAttributes(AdditionalAttributes additionalAttributes,
098                                              RequestDocument requestDocument) {
099    }
100
101    public synchronized Node initLevelNode(String name, Node parent, boolean isRecursiveCall, Session session)
102            throws Exception {
103        long existing = 0;
104        try {
105            long bucketSize = BUCKET_SIZES.get(name);
106            boolean hasRepeatedChild = HAS_REPEATED_CHILD.get(name);
107            if (parent.hasNode(name)) {
108                NodeIterator existingNodes = parent.getNodes(name);
109                existing = existingNodes.getSize();
110                if (existing <= bucketSize && !((isRecursiveCall && existing == bucketSize) || (!hasRepeatedChild
111                        && existing
112                        == bucketSize))) {
113                    if (hasRepeatedChild && !isRecursiveCall) {
114                        existingNodes.skip(existing - 1);
115                        return existingNodes.nextNode();
116                    } else {
117                        Node levelNode = initNonStaticNode(name, parent);
118                        if (existing == 0) {
119                            session.save();
120                        }
121                        return levelNode;
122                    }
123                } else {
124                    if (!STATIC_NODES.contains(parent.getPath())) {
125                        parent = initLevelNode(parent.getName(), parent.getParent(), true, session);
126                        return initNonStaticNode(name, parent);
127                    } else {
128                        throw new Exception("Node [" + parent.getName() + "/" + name + "[" + (existing + 1)
129                                + "]] Cannot Be Created. CAUSE: TREE [" + bucketSize + "] FULL ");
130                    }
131                }
132            } else {
133                return initNonStaticNode(name, parent);
134            }
135        } catch (Exception e) {
136            try {
137                logger.error(
138                        "Exception While initializing Node: " + parent.getName() + "/" + name + "[" + (existing + 1)
139                                + "] \t to Parent: " + parent.getName(), e);
140            } catch (RepositoryException e1) {
141            }
142            throw e;
143        }
144    }
145
146    @Deprecated
147    public synchronized Node initFileNode(RequestDocument document, String name, Node parentNode, Session session)
148            throws Exception {
149        String uuid = null;
150        Node fileNode = createFileNode(document, name, parentNode, session);
151        //        Node contentNode = createContentNode(fileNode, document, parentNode, session);
152        return fileNode;
153    }
154
155    public synchronized Node createFileNode(RequestDocument document, String name, Node parentNode, Session session)
156            throws OleDocStoreException {
157        Node fileNode = null;
158        try {
159            NodeIterator nodes = parentNode.getNodes(name);
160            if (nodes.getSize() >= BUCKET_SIZE_FILE_NODES) {
161                if (document != null && DocFormat.OLEML.isEqualTo(document.getFormat())) {
162                    throw new RuntimeException("FileNode creation failed as the BUCKET_SIZE[" + BUCKET_SIZE_FILE_NODES
163                            + "] is FULL: for the doc: " + document.getFormat() + "\n@ level: "
164                            + parentNode.getPath() + "/" + name + "[" + (nodes.getSize() + 1) + "]");
165                } else {
166                    parentNode = initLevelNode(parentNode.getName(), parentNode.getParent(), true, session);
167                }
168            }
169
170            fileNode = parentNode.addNode(name, "olefile");
171            fileNode.addMixin("mix:referenceable");
172            //            if (isVersioningEnabled()) {
173            //                fileNode.addMixin("mix:versionable");
174            //            }
175            //            if (DocType.LICENSE.isEqualTo(document.getType())) {
176            //                fileNode.addMixin("mix:versionable");
177            //            }
178
179            AdditionalAttributes additionalAttributes = document.getAdditionalAttributes();
180            //            if (DocType.LICENSE.isEqualTo(document.getType()) && !DocFormat.ONIXPL.isEqualTo(document.getFormat())) {
181            //                String docName = new File(document.getDocumentName()).getName();
182            //                additionalAttributes.setAttribute("dateLoaded", Calendar.getInstance().toString());
183            //                additionalAttributes.setAttribute("fileName", docName);
184            //                additionalAttributes.setAttribute("owner", document.getUser());
185            //            }
186
187            //modifyAdditionalAttributes(additionalAttributes, document);
188
189            if (additionalAttributes != null) {
190                Collection<String> attributeNames = additionalAttributes.getAttributeNames();
191                if (attributeNames != null && attributeNames.size() > 0) {
192                    for (Iterator<String> iterator = attributeNames.iterator(); iterator.hasNext(); ) {
193                        String attributeName = iterator.next();
194                        String attributeValue = additionalAttributes.getAttribute(attributeName);
195                        fileNode.setProperty(attributeName, attributeValue);
196                    }
197                }
198
199            }
200            //            else {
201            //                fileNode.setProperty("dateEntered", Calendar.getInstance());
202            //                fileNode.setProperty("lastUpdated", Calendar.getInstance());
203            //            }
204
205            if (document != null) {
206                document.setUuid(fileNode.getIdentifier());
207            }
208        } catch (Exception e) {
209            throw new OleDocStoreException("File node cannot be created.", e);
210        }
211        return fileNode;
212    }
213
214    public synchronized Node createContentNode(Node fileNode, RequestDocument document, Node parentNode,
215                                               Session session) throws OleDocStoreException {
216        Node resNode = null;
217        try {
218
219            resNode = fileNode.addNode("jcr:content", "nt:resource");
220            resNode.setProperty("jcr:mimeType", "application/xml");
221            resNode.setProperty("jcr:encoding", "");
222
223            String charset = "UTF-8";
224            byte[] documentBytes = null;
225            try {
226                //                String uuid = fileNode.getIdentifier();
227                //                document.setUuid(uuid);
228                // Content Manipulations
229                //                if (DocFormat.MARC.isEqualTo(document.getFormat())) {
230                //                    new WorkBibMarcContentHandler().doPreIngestContentManipulations(document, uuid);
231                //                }
232                //                            else if (DocFormat.OLEML.isEqualTo(document.getFormat())) {
233                //                                  WorkInstanceDocumentManager resolver = WorkInstanceDocumentManager.getInstance();
234                //                                    resolver.modifyDocumentContent(document, fileNode.getIdentifier(), parentNode.getIdentifier());
235                //                                           .doInstanceOleMLContentManipulations(document, fileNode.getIdentifier(), parentNode);
236                //                                }
237
238                if (document.getContent() != null && document.getContent().getContent() != null) {
239                    documentBytes = document.getContent().getContent().getBytes();
240                } else if (document.getDocumentName() != null) {
241                    File file = new File(document.getDocumentName());
242                    if (file.exists()) {
243                        documentBytes = FileUtils.readFileToByteArray(file);
244                    }
245                }
246            } catch (Exception e) {
247                logger.error("Failed to convert document string to byte[] with charset " + charset, e);
248            }
249            InputStream docInputStream = new ByteArrayInputStream(documentBytes);
250            Binary binary = session.getValueFactory().createBinary(docInputStream);
251            resNode.setProperty("jcr:data", binary);
252            Calendar lastModified = Calendar.getInstance();
253            lastModified.setTimeInMillis(new Date().getTime());
254            resNode.setProperty("jcr:lastModified", lastModified);
255        } catch (RepositoryException e) {
256            throw new OleDocStoreException(e);
257        }
258        return resNode;
259    }
260
261    public void enableVersioning(Node node) throws OleDocStoreException {
262        try {
263            node.addMixin("mix:versionable");
264        } catch (Exception e) {
265            throw new OleDocStoreException(e);
266        }
267    }
268
269    public Node getNodeByUUID(Session session, String uuid) throws OleDocStoreException {
270        logger.debug("Started getting node for UUID:" + uuid);
271        try {
272            return session.getNodeByIdentifier(uuid);
273        } catch (RepositoryException e) {
274            throw new OleDocStoreException("getNodeByUUID failed", e);
275        }
276    }
277
278    public String getData(Node nodeByUUID) throws OleDocStoreException, RepositoryException, FileNotFoundException {
279        StringBuffer stringBuffer = new StringBuffer();
280        if (null != nodeByUUID) {
281            Node jcrContent = nodeByUUID.getNode("jcr:content");
282            Binary binary = jcrContent.getProperty("jcr:data").getBinary();
283            InputStream content = binary.getStream();
284
285            Writer writer;
286            try {
287                writer = new StringWriter();
288                char[] buffer = new char[1024];
289                try {
290                    Reader reader = new BufferedReader(new InputStreamReader(content, "UTF-8"));
291                    int n;
292                    while ((n = reader.read(buffer)) != -1) {
293                        writer.write(buffer, 0, n);
294                    }
295                } finally {
296                    stringBuffer.append(writer.toString());
297                    content.close();
298                }
299
300            } catch (IOException e) {
301                logger.info("failure during checkOut of ", e);
302            }
303
304        }
305
306        return stringBuffer.toString();
307    }
308
309    public byte[] getBinaryData(Node nodeByUUID) throws RepositoryException, IOException {
310        byte[] bytes = null;
311        if (null != nodeByUUID) {
312            Node jcrContent = nodeByUUID.getNode("jcr:content");
313            Binary binary = jcrContent.getProperty("jcr:data").getBinary();
314            InputStream inputStream = binary.getStream();
315            bytes = getBytesFromInputStream(inputStream);
316        }
317        return bytes;
318    }
319
320    public byte[] getBytesFromInputStream(InputStream is) throws IOException {
321        long length = is.available();
322        if (length > Integer.MAX_VALUE) {
323            // File is too large
324        }
325        byte[] bytes = new byte[(int) length];
326        // Read in the bytes
327        int offset = 0;
328        int numRead = 0;
329        while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
330            offset += numRead;
331        }
332        // Ensure all the bytes have been read in
333        if (offset < bytes.length) {
334            throw new IOException("Could not completely read file ");
335        }
336        // Close the input stream and return bytes
337        is.close();
338        return bytes;
339    }
340
341    @Override
342    public void ingestItemRecForInstance(RequestDocument reqDoc, String id, Session session) throws OleDocStoreException {
343        //To change body of implemented methods use File | Settings | File Templates.
344    }
345
346    @Override
347    public String getInstanceData(Node instanceNode)
348            throws RepositoryException, OleDocStoreException, FileNotFoundException {
349        return null;  //To change body of implemented methods use File | Settings | File Templates.
350    }
351}