Java tutorial
/** * This file is part of Jahia, next-generation open source CMS: * Jahia's next-generation, open source CMS stems from a widely acknowledged vision * of enterprise application convergence - web, search, document, social and portal - * unified by the simplicity of web content management. * * For more information, please visit http://www.jahia.com. * * Copyright (C) 2002-2012 Jahia Solutions Group SA. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * As a special exception to the terms and conditions of version 2.0 of * the GPL (or any later version), you may redistribute this Program in connection * with Free/Libre and Open Source Software ("FLOSS") applications as described * in Jahia's FLOSS exception. You should have received a copy of the text * describing the FLOSS exception, and it is also available here: * http://www.jahia.com/license * * Commercial and Supported Versions of the program (dual licensing): * alternatively, commercial and supported versions of the program may be used * in accordance with the terms and conditions contained in a separate * written agreement between you and Jahia Solutions Group SA. * * If you are unsure which license is appropriate for your use, * please contact the sales department at sales@jahia.com. */ package org.jahia.services.content.impl.external; import org.apache.commons.lang.StringUtils; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.value.BinaryImpl; import org.hibernate.Criteria; import org.hibernate.HibernateException; import org.hibernate.SessionFactory; import org.hibernate.StatelessSession; import org.hibernate.classic.*; import org.hibernate.criterion.Restrictions; import org.jahia.services.content.nodetypes.*; import javax.jcr.*; import javax.jcr.Session; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.util.*; /** * User: toto * Date: Apr 23, 2008 * Time: 11:46:22 AM */ public class ExternalNodeImpl extends ExternalItemImpl implements Node { private ExternalData data; private Map<String, ExternalPropertyImpl> properties = null; public ExternalNodeImpl(ExternalData data, ExternalSessionImpl session) throws RepositoryException { super(session); this.data = data; this.properties = new HashMap<String, ExternalPropertyImpl>(); for (Map.Entry<String, String[]> entry : data.getProperties().entrySet()) { ExtendedPropertyDefinition definition = getPropertyDefinition(entry.getKey()); if (definition != null) { int requiredType = definition.getRequiredType(); if (requiredType == PropertyType.UNDEFINED) { requiredType = PropertyType.STRING; } if (definition.isMultiple()) { Value[] values = new Value[entry.getValue().length]; for (int i = 0; i < entry.getValue().length; i++) { values[i] = session.getValueFactory().createValue(entry.getValue()[i], requiredType); } properties.put(entry.getKey(), new ExternalPropertyImpl( new Name(entry.getKey(), NodeTypeRegistry.getInstance().getNamespaces()), this, session, values)); } else { properties.put(entry.getKey(), new ExternalPropertyImpl( new Name(entry.getKey(), NodeTypeRegistry.getInstance().getNamespaces()), this, session, session.getValueFactory().createValue(entry.getValue()[0], requiredType))); } } } if (data.getBinaryProperties() != null) { for (Map.Entry<String, Binary[]> entry : data.getBinaryProperties().entrySet()) { ExtendedPropertyDefinition definition = getPropertyDefinition(entry.getKey()); if (definition != null && definition.getRequiredType() == PropertyType.BINARY) { if (definition.isMultiple()) { Value[] values = new Value[entry.getValue().length]; for (int i = 0; i < entry.getValue().length; i++) { values[i] = session.getValueFactory().createValue(entry.getValue()[i]); } properties.put(entry.getKey(), new ExternalPropertyImpl( new Name(entry.getKey(), NodeTypeRegistry.getInstance().getNamespaces()), this, session, values)); } else { properties.put(entry.getKey(), new ExternalPropertyImpl( new Name(entry.getKey(), NodeTypeRegistry.getInstance().getNamespaces()), this, session, session.getValueFactory().createValue(entry.getValue()[0]))); } } } } properties.put("jcr:uuid", new ExternalPropertyImpl(new Name("jcr:uuid", NodeTypeRegistry.getInstance().getNamespaces()), this, session, session.getValueFactory().createValue(getIdentifier()))); } private ExtendedPropertyDefinition getPropertyDefinition(String name) throws RepositoryException { Map<String, ExtendedPropertyDefinition> propertyDefinitionsAsMap = getExtendedPrimaryNodeType() .getPropertyDefinitionsAsMap(); if (propertyDefinitionsAsMap.containsKey(name)) { return propertyDefinitionsAsMap.get(name); } for (NodeType nodeType : getMixinNodeTypes()) { propertyDefinitionsAsMap = ((ExtendedNodeType) nodeType).getPropertyDefinitionsAsMap(); if (propertyDefinitionsAsMap.containsKey(name)) { return propertyDefinitionsAsMap.get(name); } } if (!getExtendedPrimaryNodeType().getUnstructuredPropertyDefinitions().isEmpty()) { return getExtendedPrimaryNodeType().getUnstructuredPropertyDefinitions().values().iterator().next(); } return null; } public String getPath() throws RepositoryException { return data.getPath(); } public String getName() throws RepositoryException { return StringUtils.substringAfterLast(data.getPath(), "/"); } public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException { if (data.getPath().equals("/")) { throw new ItemNotFoundException(); } String path = StringUtils.substringBeforeLast(data.getPath(), "/"); return session.getNode(path.isEmpty() ? "/" : path); } public Item getAncestor(int depth) throws ItemNotFoundException, AccessDeniedException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public int getDepth() throws RepositoryException { throw new UnsupportedRepositoryOperationException(); } public Session getSession() throws RepositoryException { return session; } public boolean isNode() { return true; } public void remove() throws VersionException, LockException, ConstraintViolationException, RepositoryException { if (!(session.getRepository().getDataSource() instanceof ExternalDataSource.Writable)) { throw new UnsupportedRepositoryOperationException(); } session.getDeletedData().put(getPath(), data); } public void removeProperty(String name) throws RepositoryException { if (!(session.getRepository().getDataSource() instanceof ExternalDataSource.Writable)) { throw new UnsupportedRepositoryOperationException(); } data.getBinaryProperties().remove(name); data.getProperties().remove(name); session.getChangedData().put(getPath(), data); } public Node addNode(String relPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException { return addNode(relPath, null); } public Node addNode(String relPath, String primaryNodeTypeName) throws ItemExistsException, PathNotFoundException, NoSuchNodeTypeException, LockException, VersionException, ConstraintViolationException, RepositoryException { if (!(session.getRepository().getDataSource() instanceof ExternalDataSource.Writable)) { throw new UnsupportedRepositoryOperationException(); } ExternalData data = new ExternalData(this.data.getId() + "/" + relPath, getPath() + "/" + relPath, primaryNodeTypeName, new HashMap<String, String[]>()); session.getChangedData().put(data.getPath(), data); return new ExternalNodeImpl(data, session); } public void orderBefore(String srcChildRelPath, String destChildRelPath) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public Property setProperty(String name, Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (!(session.getRepository().getDataSource() instanceof ExternalDataSource.Writable)) { throw new UnsupportedRepositoryOperationException(); } data.getProperties().put(name, new String[] { value.getString() }); session.getChangedData().put(getPath(), data); return new ExternalPropertyImpl(new Name(name, NodeTypeRegistry.getInstance().getNamespaces()), this, (ExternalSessionImpl) getSession(), value); } public Property setProperty(String name, Value value, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, value); } public Property setProperty(String name, Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (!(session.getRepository().getDataSource() instanceof ExternalDataSource.Writable)) { throw new UnsupportedRepositoryOperationException(); } String[] s = null; if (values != null) { s = new String[values.length]; for (int i = 0; i < values.length; i++) { s[i] = values[i].getString(); } data.getProperties().put(name, s); session.getChangedData().put(getPath(), data); } return new ExternalPropertyImpl(new Name(name, NodeTypeRegistry.getInstance().getNamespaces()), this, (ExternalSessionImpl) getSession(), values); } public Property setProperty(String name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values); } public Property setProperty(String name, String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value[] v = null; if (values != null) { v = new Value[values.length]; for (int i = 0; i < values.length; i++) { v[i] = getSession().getValueFactory().createValue(values[i]); } } return setProperty(name, v); } public Property setProperty(String name, String[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values); } public Property setProperty(String name, String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, getSession().getValueFactory().createValue(value)); } public Property setProperty(String name, String value, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, value); } public Property setProperty(String name, InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (!(session.getRepository().getDataSource() instanceof ExternalDataSource.Writable)) { throw new UnsupportedRepositoryOperationException(); } Value v = null; try { Binary[] b = { new BinaryImpl(value) }; data.getBinaryProperties().put(name, b); v = getSession().getValueFactory().createValue(new BinaryImpl(value)); } catch (IOException e) { throw new RepositoryException(e); } session.getChangedData().put(getPath(), data); return new ExternalPropertyImpl(new Name(name, NodeTypeRegistry.getInstance().getNamespaces()), this, (ExternalSessionImpl) getSession(), v); } public Property setProperty(String name, boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = getSession().getValueFactory().createValue(value); return setProperty(name, v); } public Property setProperty(String name, double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = getSession().getValueFactory().createValue(value); return setProperty(name, v); } public Property setProperty(String name, long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = getSession().getValueFactory().createValue(value); return setProperty(name, v); } public Property setProperty(String name, Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = getSession().getValueFactory().createValue(value); return setProperty(name, v); } public Property setProperty(String name, Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = getSession().getValueFactory().createValue(value); return setProperty(name, v); } public Node getNode(String s) throws PathNotFoundException, RepositoryException { String path = getPath().endsWith("/") ? getPath() : getPath() + "/"; return session.getNode(path + s); } public NodeIterator getNodes() throws RepositoryException { List<String> l = session.getRepository().getDataSource().getChildren(getPath()); if (data.getI18nProperties() != null) { l = new ArrayList<String>(l); for (String lang : data.getI18nProperties().keySet()) { l.add("j:translation_" + lang); } } return new ExternalNodeIterator(l); } public NodeIterator getNodes(String namePattern) throws RepositoryException { final List<String> l = session.getRepository().getDataSource().getChildren(getPath()); final List<String> filteredList = new ArrayList<String>(); for (String path : l) { if (ChildrenCollectorFilter.matches(path, namePattern)) { filteredList.add(path); } } if (data.getI18nProperties() != null) { for (String lang : data.getI18nProperties().keySet()) { filteredList.add("j:translation_" + lang); } } return new ExternalNodeIterator(filteredList); } public Property getProperty(String s) throws PathNotFoundException, RepositoryException { Property property = properties.get(s); if (property == null) { throw new PathNotFoundException(s); } return property; } public PropertyIterator getProperties() throws RepositoryException { return new ExternalPropertyIterator(properties); } public PropertyIterator getProperties(String namePattern) throws RepositoryException { final Map<String, ExternalPropertyImpl> filteredList = new HashMap<String, ExternalPropertyImpl>(); for (Map.Entry<String, ExternalPropertyImpl> entry : properties.entrySet()) { if (ChildrenCollectorFilter.matches(entry.getKey(), namePattern)) { filteredList.put(entry.getKey(), entry.getValue()); } } return new ExternalPropertyIterator(filteredList); } public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { throw new ItemNotFoundException("External node does not support getPrimaryItem"); } public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { return getIdentifier(); } public int getIndex() throws RepositoryException { return 0; } public PropertyIterator getReferences() throws RepositoryException { return new ExternalPropertyIterator(new HashMap<String, ExternalPropertyImpl>()); } public boolean hasNode(String s) throws RepositoryException { try { getNode(s); return true; } catch (PathNotFoundException e) { return false; } } public boolean hasProperty(String relPath) throws RepositoryException { return properties.containsKey(relPath); } public boolean hasNodes() throws RepositoryException { return getNodes().hasNext(); } public boolean hasProperties() throws RepositoryException { return !properties.isEmpty(); } public NodeType getPrimaryNodeType() throws RepositoryException { return getExtendedPrimaryNodeType(); } public ExtendedNodeType getExtendedPrimaryNodeType() throws RepositoryException { return NodeTypeRegistry.getInstance().getNodeType(data.getType()); } public NodeType[] getMixinNodeTypes() throws RepositoryException { List<NodeType> nt = new ArrayList<NodeType>(); if (data.getMixin() != null) { for (String s : data.getMixin()) { nt.add(NodeTypeRegistry.getInstance().getNodeType(s)); } } return nt.toArray(new NodeType[nt.size()]); } public boolean isNodeType(String nodeTypeName) throws RepositoryException { if (getPrimaryNodeType().isNodeType(nodeTypeName)) { return true; } for (NodeType nodeType : getMixinNodeTypes()) { if (nodeType.isNodeType(nodeTypeName)) { return true; } } return false; } public void addMixin(String s) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public void removeMixin(String s) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public boolean canAddMixin(String s) throws NoSuchNodeTypeException, RepositoryException { return false; } public NodeDefinition getDefinition() throws RepositoryException { ExternalNodeImpl parentNode = (ExternalNodeImpl) getParent(); ExtendedNodeType parentNodeType = parentNode.getExtendedPrimaryNodeType(); ExtendedNodeDefinition nodeDefinition = parentNodeType.getNodeDefinition(getPrimaryNodeType().getName()); if (nodeDefinition != null) { return nodeDefinition; } for (ExtendedNodeDefinition extendedNodeDefinition : parentNodeType.getUnstructuredChildNodeDefinitions() .values()) { return extendedNodeDefinition; } return null; } public Version checkin() throws VersionException, UnsupportedRepositoryOperationException, InvalidItemStateException, LockException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public void checkout() throws UnsupportedRepositoryOperationException, LockException, RepositoryException { } public void doneMerge(Version version) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public void cancelMerge(Version version) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public void update(String s) throws NoSuchWorkspaceException, AccessDeniedException, LockException, InvalidItemStateException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public NodeIterator merge(String s, boolean b) throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { return getPath(); } public boolean isCheckedOut() throws RepositoryException { return true; } public void restore(String s, boolean b) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public void restore(Version version, boolean b) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public void restore(Version version, String s, boolean b) throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public void restoreByLabel(String s, boolean b) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public Version getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public Lock lock(boolean b, boolean b1) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { throw new UnsupportedRepositoryOperationException("Locking is not supported on External repository"); } public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { throw new UnsupportedRepositoryOperationException("Locking is not supported on External repository"); } public boolean holdsLock() throws RepositoryException { return false; } public boolean isLocked() throws RepositoryException { return false; } public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, value.getStream()); } public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { Value v = getSession().getValueFactory().createValue(value); return setProperty(name, v); } public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { final List<String> l = session.getRepository().getDataSource().getChildren(getPath()); final List<String> filteredList = new ArrayList<String>(); for (String path : l) { if (ChildrenCollectorFilter.matches(path, nameGlobs)) { filteredList.add(path); } } return new ExternalNodeIterator(filteredList); } public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { final Map<String, ExternalPropertyImpl> filteredList = new HashMap<String, ExternalPropertyImpl>(); for (Map.Entry<String, ExternalPropertyImpl> entry : properties.entrySet()) { if (ChildrenCollectorFilter.matches(entry.getKey(), nameGlobs)) { filteredList.put(entry.getKey(), entry.getValue()); } } return new ExternalPropertyIterator(filteredList); } public String getIdentifier() throws RepositoryException { if (!session.getRepository().getDataSource().isSupportsUuid() || data.getId().startsWith("translation:")) { SessionFactory hibernateSession = ((ExternalSessionImpl) getSession()).getRepository() .getStoreProvider().getHibernateSession(); org.hibernate.classic.Session statelessSession = hibernateSession.openSession(); try { Criteria criteria = statelessSession.createCriteria(UuidMapping.class); String key = ((ExternalSessionImpl) getSession()).getRepository().getStoreProvider().getKey(); criteria.add(Restrictions.eq("externalId", data.getId())).add(Restrictions.eq("providerKey", key)); List list = criteria.list(); if (list.size() > 0) { return ((UuidMapping) list.get(0)).getInternalUuid(); } else { UUID uuid = UUID.randomUUID(); UuidMapping uuidMapping = new UuidMapping(); uuidMapping.setExternalId(data.getId()); uuidMapping.setProviderKey(key); uuidMapping.setInternalUuid(uuid.toString()); try { statelessSession.beginTransaction(); statelessSession.save(uuidMapping); statelessSession.getTransaction().commit(); } catch (HibernateException e) { statelessSession.getTransaction().rollback(); } return uuid.toString(); } } finally { statelessSession.close(); } } else { return data.getId(); } } public PropertyIterator getReferences(String name) throws RepositoryException { return new ExternalPropertyIterator(new HashMap<String, ExternalPropertyImpl>()); } public PropertyIterator getWeakReferences() throws RepositoryException { return new ExternalPropertyIterator(new HashMap<String, ExternalPropertyImpl>()); } public PropertyIterator getWeakReferences(String name) throws RepositoryException { return new ExternalPropertyIterator(new HashMap<String, ExternalPropertyImpl>()); } public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public NodeIterator getSharedSet() throws RepositoryException { return new ExternalNodeIterator(new ArrayList<String>()); } public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { return new String[0]; } private class ExternalPropertyIterator implements PropertyIterator { int pos; private final Iterator<ExternalPropertyImpl> it; private Map<String, ExternalPropertyImpl> properties; public ExternalPropertyIterator(Map<String, ExternalPropertyImpl> properties) { this.properties = properties; this.it = properties.values().iterator(); pos = 0; } public Property nextProperty() { pos++; return it.next(); } public void skip(long skipNum) { for (int i = 0; i < skipNum; i++) { nextProperty(); } pos += skipNum; } public long getSize() { return properties.size(); } public long getPosition() { return pos; } public boolean hasNext() { return it.hasNext(); } public Object next() { return nextProperty(); } public void remove() { throw new UnsupportedOperationException(); } } private class ExternalNodeIterator implements NodeIterator { private int pos; private final Iterator<String> it; private final List<String> list; public ExternalNodeIterator(List<String> list) { this.list = list; it = list.iterator(); pos = 0; } public Node nextNode() { pos++; try { return getNode(it.next()); } catch (RepositoryException e) { e.printStackTrace(); return null; } } public void skip(long skipNum) { for (int i = 0; i < skipNum; i++) { it.next(); } pos += skipNum; } public long getSize() { return list.size(); } public long getPosition() { return pos; } public boolean hasNext() { return it.hasNext(); } public Object next() { return nextNode(); } public void remove() { throw new UnsupportedOperationException(); } } }