View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.cms.core;
35  
36  import info.magnolia.cms.core.version.ContentVersion;
37  import info.magnolia.cms.core.version.VersionManager;
38  import info.magnolia.cms.security.AccessDeniedException;
39  import info.magnolia.cms.security.Permission;
40  import info.magnolia.cms.util.Rule;
41  import info.magnolia.context.MgnlContext;
42  import info.magnolia.context.MgnlContext.Op;
43  import info.magnolia.logging.AuditLoggingUtil;
44  
45  import java.util.ArrayList;
46  import java.util.Collection;
47  import java.util.Collections;
48  import java.util.Comparator;
49  import java.util.List;
50  
51  import javax.jcr.Item;
52  import javax.jcr.ItemNotFoundException;
53  import javax.jcr.Node;
54  import javax.jcr.NodeIterator;
55  import javax.jcr.PathNotFoundException;
56  import javax.jcr.Property;
57  import javax.jcr.PropertyIterator;
58  import javax.jcr.PropertyType;
59  import javax.jcr.RepositoryException;
60  import javax.jcr.UnsupportedRepositoryOperationException;
61  import javax.jcr.lock.Lock;
62  import javax.jcr.lock.LockException;
63  import javax.jcr.nodetype.NodeType;
64  import javax.jcr.version.Version;
65  import javax.jcr.version.VersionException;
66  import javax.jcr.version.VersionHistory;
67  import javax.jcr.version.VersionIterator;
68  
69  import org.apache.commons.lang.StringUtils;
70  import org.slf4j.Logger;
71  import org.slf4j.LoggerFactory;
72  
73  
74  /**
75   * Default, JCR-based, implementation of {@link Content}.
76   *
77   * @author Sameer Charles
78   * @version $Revision:2719 $ ($Author:scharles $)
79   */
80  public class DefaultContent extends AbstractContent {
81      private static final Logger log = LoggerFactory.getLogger(DefaultContent.class);
82  
83      /**
84       * Wrapped jcr node.
85       */
86      protected Node node;
87  
88      /**
89       * Path for the jcr node.
90       */
91      private String path;
92  
93      /**
94       * root node.
95       */
96      private Node rootNode;
97  
98      /**
99       * node metadata.
100      */
101     private MetaData metaData;
102 
103     /**
104      * Empty constructor. Should NEVER be used for standard use, test only.
105      */
106     protected DefaultContent() {
107     }
108 
109     /**
110      * Constructor to get existing node.
111      * @param rootNode node to start with
112      * @param path absolute (primary) path to this <code>Node</code>
113      * @param hierarchyManager HierarchyManager instance
114      * @throws PathNotFoundException if the node at <code>path</code> does not exist
115      * @throws AccessDeniedException if the current session does not have sufficient access rights to complete the
116      * operation
117      * @throws RepositoryException if an error occurs
118      */
119     DefaultContent(Node rootNode, String path, HierarchyManager hierarchyManager) throws PathNotFoundException, RepositoryException, AccessDeniedException {
120         this.setHierarchyManager(hierarchyManager);
121         Access.isGranted(hierarchyManager.getAccessManager(), Path.getAbsolutePath(rootNode.getPath(), path), Permission.READ);
122         this.setPath(path);
123         this.setRootNode(rootNode);
124         this.setNode(this.rootNode.getNode(this.path));
125     }
126 
127     /**
128      * Constructor to get existing node.
129      * @param elem initialized node object
130      * @param hierarchyManager HierarchyManager instance
131      * @throws AccessDeniedException if the current session does not have sufficient access rights to complete the
132      * operation
133      * @throws RepositoryException if an error occurs
134      */
135     public DefaultContent(Item elem,HierarchyManager hierarchyManager) throws RepositoryException, AccessDeniedException {
136         this.setHierarchyManager(hierarchyManager);
137         Access.isGranted(hierarchyManager.getAccessManager(), Path.getAbsolutePath(elem.getPath()), Permission.READ);
138         this.setNode((Node) elem);
139         this.setPath(this.getHandle());
140     }
141 
142     /**
143      * Creates contentNode of type <b>contentType </b> contentType must be defined in item type definition of Magnolia
144      * as well as JCR implementation.
145      * @param rootNode node to start with
146      * @param path absolute (primary) path to this <code>Node</code>
147      * @param contentType JCR node type as configured
148      * @param hierarchyManager HierarchyManager instance
149      * @throws PathNotFoundException if the node at <code>path</code> does not exist
150      * @throws RepositoryException if an error occurs
151      * @throws AccessDeniedException if the current session does not have sufficient access rights to complete the
152      * operation
153      */
154     DefaultContent(Node rootNode, String path, String contentType, HierarchyManager hierarchyManager)
155     throws PathNotFoundException,
156     RepositoryException,
157     AccessDeniedException {
158         this.setHierarchyManager(hierarchyManager);
159         Access.isGranted(hierarchyManager.getAccessManager(), Path.getAbsolutePath(rootNode.getPath(), path), Permission.WRITE);
160         this.setPath(path);
161         this.setRootNode(rootNode);
162         this.node = this.rootNode.addNode(this.path, contentType);
163         // add mix:lockable as default for all nodes created using this manager
164         // for version 3.5 we cannot change node type definitions because of compatibility reasons
165         // MAGNOLIA-1518
166         this.addMixin(ItemType.MIX_LOCKABLE);
167         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_CREATE, hierarchyManager.getName(), this.getItemType(), Path.getAbsolutePath(node.getPath()));
168     }
169 
170     /**
171      * @param node
172      */
173     protected void setNode(Node node) {
174         this.node = node;
175     }
176 
177     /**
178      * @param node
179      */
180     protected void setRootNode(Node node) {
181         this.rootNode = node;
182     }
183 
184     /**
185      * @param path
186      */
187     protected void setPath(String path) {
188         this.path = path;
189     }
190 
191     public Content getContent(String name) throws PathNotFoundException, RepositoryException, AccessDeniedException {
192         return (new DefaultContent(this.node, name, this.getHierarchyManager()));
193     }
194 
195     public Content createContent(String name, String contentType) throws PathNotFoundException, RepositoryException,
196     AccessDeniedException {
197         Content content = new DefaultContent(this.node, name, contentType, this.getHierarchyManager());
198         MetaData metaData = content.getMetaData();
199         metaData.setCreationDate();
200         return content;
201     }
202 
203     @Override
204     public boolean hasNodeData(String name) throws RepositoryException {
205         if (this.node.hasProperty(name)) {
206             return true;
207         }
208         else { // check for mgnl:resource node
209             if (this.node.hasNode(name) && (this.node.getNode(name).isNodeType(ItemType.NT_RESOURCE) || (this.node.hasProperty("jcr:frozenPrimaryType") && this.node.getNode(name).getProperty("jcr:frozenPrimaryType").getValue().getString().equals(ItemType.NT_RESOURCE)))) {
210                 return true;
211             }
212         }
213         return false;
214     }
215 
216     @Override
217     public NodeData newNodeDataInstance(String name, int type, boolean createIfNotExisting) throws AccessDeniedException, RepositoryException {
218         try {
219             Access.isGranted(getAccessManager(), Path.getAbsolutePath(getHandle(), name), Permission.READ);
220         }
221         // FIXME: should be thrown but return a dummy node data
222         catch (AccessDeniedException e) {
223             return new NonExistingNodeData(this, name);
224         }
225 
226         // create an empty dummy
227         if(!hasNodeData(name) && !createIfNotExisting){
228             return new NonExistingNodeData(this, name);
229         }
230 
231         if(type == PropertyType.UNDEFINED){
232             type = determineNodeDataType(name);
233         }
234 
235         if(type == PropertyType.BINARY){
236             return new BinaryNodeData(this, name);
237         }
238         else{
239             return new DefaultNodeData(this, name);
240         }
241     }
242 
243     protected int determineNodeDataType(String name) {
244         // FIXME: maybe delegate to NodeDataImplementations?
245         try {
246             if (this.node.hasProperty(name)) {
247                 return this.node.getProperty(name).getType();
248             }
249             else { // check for mgnl:resource node
250                 if (this.node.hasNode(name) && (this.node.getNode(name).isNodeType(ItemType.NT_RESOURCE) || (this.node.getNode(name).hasProperty("jcr:frozenPrimaryType") && this.node.getNode(name).getProperty("jcr:frozenPrimaryType").getValue().getString().equals(ItemType.NT_RESOURCE)))) {
251                     return PropertyType.BINARY;
252                 }
253             }
254         }
255         catch (RepositoryException e) {
256             throw new IllegalStateException("Can't determine property type of [" + getHandle() + "/" + name + "]", e);
257         }
258         return PropertyType.UNDEFINED;
259     }
260 
261 
262     public MetaData getMetaData() {
263         if (this.metaData == null) {
264             this.metaData = new MetaData(this.node, this.getHierarchyManager().getAccessManager());
265         }
266         return this.metaData;
267     }
268 
269     public String getName() {
270         try {
271             return this.node.getName();
272         }
273         catch (RepositoryException e) {
274             log.error(e.getMessage(), e);
275         }
276         return StringUtils.EMPTY;
277     }
278 
279     @Override
280     public Collection<Content> getChildren(ContentFilter filter, String namePattern, Comparator<Content> orderCriteria) {
281         List<Content> children;
282         children = new ArrayList<Content>();
283 
284         try {
285             final NodeIterator nodeIterator;
286             if (namePattern == null) {
287                 nodeIterator = this.node.getNodes();
288             } else {
289                 nodeIterator = this.node.getNodes(namePattern);
290             }
291 
292             while (nodeIterator.hasNext()) {
293                 Node subNode = (Node) nodeIterator.next();
294                 try {
295                     Content content = new DefaultContent(subNode, this.getHierarchyManager());
296                     if (filter.accept(content)) {
297                         children.add(content);
298                     }
299                 }
300                 catch (PathNotFoundException e) {
301                     log.error("Exception caught", e);
302                 }
303                 catch (AccessDeniedException e) {
304                     // ignore, simply wont add content in a list
305                 }
306             }
307         }
308         catch (RepositoryException re) {
309             log.error("Exception caught", re);
310         }
311 
312         if (orderCriteria != null) {
313             // stable sort - do not reorder or remove equal items
314             Collections.sort(children, orderCriteria);
315         }
316         return children;
317     }
318 
319     public Collection<NodeData> getNodeDataCollection(String namePattern) {
320         final ArrayList<NodeData> all = new ArrayList<NodeData>();
321         try {
322             all.addAll(getPrimitiveNodeDatas(namePattern));
323             all.addAll(getBinaryNodeDatas(namePattern));
324         }
325         catch (RepositoryException e) {
326             throw new IllegalStateException("Can't read node datas of " + toString(), e);
327         }
328         return all;
329     }
330 
331     protected Collection<NodeData> getPrimitiveNodeDatas(String namePattern) throws RepositoryException {
332         final Collection<NodeData> nodeDatas = new ArrayList<NodeData>();
333         final PropertyIterator propertyIterator;
334         if (namePattern == null) {
335             propertyIterator = this.node.getProperties();
336         } else {
337             propertyIterator = this.node.getProperties(namePattern);
338         }
339         while (propertyIterator.hasNext()) {
340             Property property = (Property) propertyIterator.next();
341             try {
342                 if (!property.getName().startsWith("jcr:") && !property.getName().startsWith("mgnl:")) { //$NON-NLS-1$ //$NON-NLS-2$
343                     nodeDatas.add(getNodeData(property.getName()));
344                 }
345             }
346             catch (PathNotFoundException e) {
347                 log.error("Exception caught", e);
348             }
349             catch (AccessDeniedException e) {
350                 // ignore, simply wont add content in a list
351             }
352         }
353         return nodeDatas;
354     }
355 
356 
357     public boolean hasContent(String name) throws RepositoryException {
358         return this.node.hasNode(name);
359     }
360 
361     public String getHandle() {
362         try {
363             return this.node.getPath();
364         }
365         catch (RepositoryException e) {
366             log.error("Failed to get handle: " + e.getMessage(), e); //$NON-NLS-1$
367             return StringUtils.EMPTY;
368         }
369     }
370 
371     public Content getParent() throws PathNotFoundException, RepositoryException, AccessDeniedException {
372         return (new DefaultContent(this.node.getParent(), this.getHierarchyManager()));
373     }
374 
375     public Content getAncestor(int level) throws PathNotFoundException, RepositoryException, AccessDeniedException {
376         if (level > this.getLevel()) {
377             throw new PathNotFoundException();
378         }
379         return (new DefaultContent(this.node.getAncestor(level), this.getHierarchyManager()));
380     }
381 
382     public Collection<Content> getAncestors() throws PathNotFoundException, RepositoryException {
383         List<Content> allAncestors = new ArrayList<Content>();
384         int level = this.getLevel();
385         while (level != 0) {
386             try {
387                 allAncestors.add(getAncestor(--level));
388             }
389             catch (AccessDeniedException e) {
390                 // valid
391             }
392         }
393         return allAncestors;
394     }
395 
396     public int getLevel() throws PathNotFoundException, RepositoryException {
397         return this.node.getDepth();
398     }
399 
400     public void orderBefore(String srcName, String beforeName) throws RepositoryException {
401         this.node.orderBefore(srcName, beforeName);
402     }
403 
404     public int getIndex() throws RepositoryException {
405         return this.node.getIndex();
406     }
407 
408     public Node getJCRNode() {
409         return this.node;
410     }
411 
412     public boolean isNodeType(String type) {
413         return isNodeType(this.node, type);
414     }
415 
416     /**
417      * private Helper method to evaluate primary node type of the given node.
418      * @param node
419      * @param type
420      */
421     protected boolean isNodeType(Node node, String type) {
422         try {
423             final String actualType = node.getProperty(ItemType.JCR_PRIMARY_TYPE).getString();
424             // if the node is frozen, and we're not looking specifically for frozen nodes, then we compare with the original node type
425             if (ItemType.NT_FROZENNODE.equals(actualType) && !(ItemType.NT_FROZENNODE.equals(type))) {
426                 final Property p = node.getProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE);
427                 final String s = p.getString();
428                 return s.equalsIgnoreCase(type);
429             } else {
430                 return node.isNodeType(type);
431             }
432         }
433         catch (RepositoryException re) {
434             log.error(re.getMessage());
435             log.debug(re.getMessage(), re);
436             return false;
437         }
438     }
439 
440     public NodeType getNodeType() throws RepositoryException {
441         return this.node.getPrimaryNodeType();
442     }
443 
444     public String getNodeTypeName() throws RepositoryException {
445 
446         if (this.node.hasProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE)) {
447             return this.node.getProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE).getString();
448         }
449         return this.node.getProperty(ItemType.JCR_PRIMARY_TYPE).getString();
450     }
451 
452     public ItemType getItemType() throws RepositoryException {
453         return new ItemType(getNodeTypeName());
454     }
455 
456     public void restore(String versionName, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
457         Access.isGranted(this.getHierarchyManager().getAccessManager(), this.getHandle(), Permission.WRITE);
458         Version version = this.getVersionHistory().getVersion(versionName);
459         this.restore(version, removeExisting);
460     }
461 
462     public void restore(Version version, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
463         Access.isGranted(this.getHierarchyManager().getAccessManager(), this.getHandle(), Permission.WRITE);
464         VersionManager.getInstance().restore(this, version, removeExisting);
465     }
466 
467     public void restore(Version version, String relPath, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
468         throw new UnsupportedRepositoryOperationException("Not implemented in 3.0 Beta");
469     }
470 
471     public void restoreByLabel(String versionLabel, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
472         this.node.restoreByLabel(versionLabel, removeExisting);
473         throw new UnsupportedRepositoryOperationException("Not implemented in 3.0 Beta");
474     }
475 
476     public Version addVersion() throws UnsupportedRepositoryOperationException, RepositoryException {
477         return VersionManager.getInstance().addVersion(this);
478     }
479 
480     public Version addVersion(Rule rule) throws UnsupportedRepositoryOperationException, RepositoryException {
481         return VersionManager.getInstance().addVersion(this, rule);
482     }
483 
484     /**
485      * Returns true if this node is either
486      * <ul>
487      * <li/>versionable and currently checked-out, <li/>non-versionable and its nearest versionable ancestor is
488      * checked-out or <li/>non-versionable and it has no versionable ancestor.
489      * </ul>
490      * Returns false if this node is either
491      * <ul>
492      * <li/>versionable and currently checked-in or <li/>non-versionable and its nearest versionable ancestor is
493      * checked-in.
494      * </ul>
495      * @return true if the node is checked out
496      * @throws RepositoryException
497      */
498     protected boolean isCheckedOut() throws RepositoryException {
499         return this.node.isCheckedOut();
500     }
501 
502     public boolean isModified() {
503         return this.node.isModified();
504     }
505 
506     public VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException {
507         return VersionManager.getInstance().getVersionHistory(this);
508     }
509 
510     public VersionIterator getAllVersions() throws UnsupportedRepositoryOperationException, RepositoryException {
511         return VersionManager.getInstance().getAllVersions(this);
512     }
513 
514     public ContentVersion getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException {
515         return new ContentVersion(VersionManager.getInstance().getBaseVersion(this), this);
516     }
517 
518     public ContentVersion getVersionedContent(Version version) throws RepositoryException {
519         return new ContentVersion(version, this);
520     }
521 
522     public ContentVersion getVersionedContent(String versionName) throws RepositoryException {
523         return new ContentVersion(VersionManager.getInstance().getVersion(this, versionName), this);
524     }
525 
526     public void removeVersionHistory() throws AccessDeniedException, RepositoryException {
527         Access.isGranted(this.getHierarchyManager().getAccessManager(), Path.getAbsolutePath(node.getPath()), Permission.WRITE);
528         VersionManager.getInstance().removeVersionHistory(this.node.getUUID());
529     }
530 
531     public void save() throws RepositoryException {
532         this.node.save();
533     }
534 
535     public void delete() throws RepositoryException {
536         final String nodePath = Path.getAbsolutePath(this.node.getPath());
537         log.debug("removing {} from {}", this.node.getPath(), getHierarchyManager().getName());
538         Access.isGranted(getHierarchyManager().getAccessManager(), nodePath, Permission.REMOVE);
539         final ItemType nodeType = this.getItemType();
540         if (!getHierarchyManager().getName().equals("mgnlVersion")) {
541             MgnlContext.doInSystemContext(new Op<Void, RepositoryException>() {
542                 public Void exec() throws RepositoryException {
543                     try {
544                         final String uuid = node.getUUID();
545                         // VersionManager.getInstance().removeVersionHistory(uuid);
546 
547                         HierarchyManager hm = MgnlContext.getHierarchyManager("mgnlVersion");
548                         Node versionedNode = hm.getContentByUUID(uuid).getJCRNode();
549                         log.debug("Located versioned node {}({})", uuid, nodePath);
550 
551                         VersionHistory history = versionedNode.getVersionHistory();
552 
553                         log.debug("Removing versioned node {}({})", uuid, nodePath);
554                         versionedNode.remove();
555                         hm.save();
556 
557                         VersionIterator iter = history.getAllVersions();
558                         // skip root version. It can't be deleted manually, but will be cleaned up automatically after removing all other versions (see JCR-134)
559                         iter.nextVersion();
560                         while (iter.hasNext()) {
561                             Version version = iter.nextVersion();
562                             log.debug("removing version {}", version.getName());
563                             history.removeVersion(version.getName());
564                         }
565                         // at this point history should be deleted automatically (at least on JR)
566                     } catch (ItemNotFoundException e) {
567                         // doesn't exist in version store, ignore
568                     } catch (UnsupportedRepositoryOperationException e) {
569                         // not versionable or not referenceable ... either way ignore
570                     }
571                     return null;
572                 }
573             });
574         }
575         this.node.remove();
576         AuditLoggingUtil.log(AuditLoggingUtil.ACTION_DELETE, getHierarchyManager().getName(), nodeType, nodePath);
577     }
578 
579 
580     public void refresh(boolean keepChanges) throws RepositoryException {
581         this.node.refresh(keepChanges);
582     }
583 
584     public String getUUID() {
585         try {
586             return this.node.getUUID();
587         }
588         catch (UnsupportedOperationException e) {
589             log.error(e.getMessage());
590         }
591         catch (RepositoryException re) {
592             log.error("Exception caught", re);
593         }
594         return StringUtils.EMPTY;
595     }
596 
597     public void addMixin(String type) throws RepositoryException {
598         Access.isGranted(this.getHierarchyManager().getAccessManager(), Path.getAbsolutePath(this.node.getPath()), Permission.SET);
599         // TODO: there seems to be bug somewhere as we are able to add mixins even when the method below returns false
600         if (!this.node.canAddMixin(type)) {
601             log.debug("Node - " + this.node.getPath() + " does not allow mixin type - " + type); //$NON-NLS-1$ //$NON-NLS-2$
602         }
603         try {
604             this.node.addMixin(type);
605         } catch (Exception e) {
606             log.error("Failed to add  mixin type - " + type + " to a node " + this.node.getPath()); //$NON-NLS-1$ //$NON-NLS-2$
607         }
608     }
609 
610     public void removeMixin(String type) throws RepositoryException {
611         Access.isGranted(this.getHierarchyManager().getAccessManager(), Path.getAbsolutePath(this.node.getPath()), Permission.SET);
612         this.node.removeMixin(type);
613     }
614 
615     public NodeType[] getMixinNodeTypes() throws RepositoryException {
616         return this.node.getMixinNodeTypes();
617     }
618 
619     public Lock lock(boolean isDeep, boolean isSessionScoped) throws LockException, RepositoryException {
620         return this.node.lock(isDeep, isSessionScoped);
621     }
622 
623     public Lock lock(boolean isDeep, boolean isSessionScoped, long yieldFor) throws LockException, RepositoryException {
624         long finalTime = System.currentTimeMillis() + yieldFor;
625         LockException lockException = null;
626         while (System.currentTimeMillis() <= finalTime) {
627             try {
628                 return this.node.lock(isDeep, isSessionScoped);
629             }
630             catch (LockException e) {
631                 // its not an exception yet, still got time
632                 lockException = e;
633             }
634             Thread.yield();
635         }
636         // could not get lock
637         throw lockException;
638     }
639 
640     public Lock getLock() throws LockException, RepositoryException {
641         return this.node.getLock();
642     }
643 
644     public void unlock() throws LockException, RepositoryException {
645         this.node.unlock();
646     }
647 
648     public boolean holdsLock() throws RepositoryException {
649         return this.node.holdsLock();
650     }
651 
652     public boolean isLocked() throws RepositoryException {
653         return this.node.isLocked();
654     }
655 
656     public boolean hasMetaData() {
657         try {
658             return this.node.hasNode("MetaData");
659         }
660         catch (RepositoryException re) {
661             log.debug(re.getMessage(), re);
662         }
663         return false;
664     }
665 
666     public boolean hasMixin(String mixinName) throws RepositoryException {
667         if (StringUtils.isBlank(mixinName)) {
668             throw new IllegalArgumentException("Mixin name can't be empty.");
669         }
670         for (NodeType type : getMixinNodeTypes()) {
671             if (mixinName.equals(type.getName())) {
672                 return true;
673             }
674         }
675         return false;
676     }
677 }