/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Florent Guillaume */ package org.eclipse.ecr.core.storage.sql.coremodel; import java.io.InputStream; import java.io.Serializable; import java.text.DateFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.resource.ResourceException; import javax.transaction.xa.XAResource; import org.eclipse.ecr.core.api.CoreSession; import org.eclipse.ecr.core.api.DocumentException; import org.eclipse.ecr.core.api.IterableQueryResult; import org.eclipse.ecr.core.api.Lock; import org.eclipse.ecr.core.api.VersionModel; import org.eclipse.ecr.core.model.Document; import org.eclipse.ecr.core.model.NoSuchDocumentException; import org.eclipse.ecr.core.model.NoSuchPropertyException; import org.eclipse.ecr.core.model.Property; import org.eclipse.ecr.core.model.Repository; import org.eclipse.ecr.core.model.Session; import org.eclipse.ecr.core.query.FilterableQuery; import org.eclipse.ecr.core.query.Query; import org.eclipse.ecr.core.query.QueryException; import org.eclipse.ecr.core.query.QueryFilter; import org.eclipse.ecr.core.query.QueryParseException; import org.eclipse.ecr.core.query.QueryResult; import org.eclipse.ecr.core.query.UnsupportedQueryTypeException; import org.eclipse.ecr.core.query.sql.NXQL; import org.eclipse.ecr.core.schema.DocumentType; import org.eclipse.ecr.core.schema.SchemaManager; import org.eclipse.ecr.core.schema.TypeConstants; import org.eclipse.ecr.core.schema.TypeProvider; import org.eclipse.ecr.core.schema.types.ComplexType; import org.eclipse.ecr.core.schema.types.CompositeType; import org.eclipse.ecr.core.schema.types.Field; import org.eclipse.ecr.core.schema.types.ListType; import org.eclipse.ecr.core.schema.types.Type; import org.eclipse.ecr.core.security.SecurityManager; import org.eclipse.ecr.core.storage.PartialList; import org.eclipse.ecr.core.storage.StorageException; import org.eclipse.ecr.core.storage.sql.Binary; import org.eclipse.ecr.core.storage.sql.CollectionProperty; import org.eclipse.ecr.core.storage.sql.Model; import org.eclipse.ecr.core.storage.sql.Node; import org.eclipse.ecr.core.storage.sql.SimpleProperty; /** * This class is the bridge between the Nuxeo SPI Session and the actual * low-level implementation of the SQL storage session. * * @author Florent Guillaume */ public class SQLSession implements Session { private final Repository repository; private final Map<String, Serializable> context; private final org.eclipse.ecr.core.storage.sql.Session session; private Document root; private final String userSessionId; public SQLSession(org.eclipse.ecr.core.storage.sql.Session session, Repository repository, Map<String, Serializable> context) throws DocumentException { this.session = session; this.repository = repository; if (context == null) { context = new HashMap<String, Serializable>(); } this.context = context; context.put("creationTime", Long.valueOf(System.currentTimeMillis())); Node rootNode; try { rootNode = session.getRootNode(); } catch (StorageException e) { throw new DocumentException(e); } root = newDocument(rootNode); userSessionId = (String) context.get("SESSION_ID"); } /* * ----- org.eclipse.ecr.core.model.Session ----- */ @Override public Document getRootDocument() { return root; } @Override public Document getNullDocument() { return new SQLDocumentLive(null, null, null, this, true); } // not called @Override public XAResource getXAResource() { throw new RuntimeException(); } @Override public void close() throws DocumentException { try { session.save(); } catch (StorageException e) { throw new DocumentException(e); } dispose(); } @Override public void save() throws DocumentException { try { session.save(); } catch (StorageException e) { throw new DocumentException(e); } } @Override public void cancel() throws DocumentException { // TODO // throw new UnsupportedOperationException(); } @Override public boolean isLive() { return session != null && session.isLive(); } @Override public void dispose() { try { session.close(); } catch (ResourceException e) { throw new RuntimeException(e); } root = null; } // not used? @Override public long getSessionId() { throw new RuntimeException(); // return sid; } @Override public String getUserSessionId() { return userSessionId; } @Override public Repository getRepository() { return repository; } @Override public Map<String, Serializable> getSessionContext() { return context; } @Override public SchemaManager getTypeManager() { return repository.getTypeManager(); } @Override public SecurityManager getSecurityManager() { return repository.getNuxeoSecurityManager(); } @Override public Document getDocumentByUUID(String uuid) throws DocumentException { try { /** * Document ids coming from higher level have been turned into * strings (by {@link SQLDocument#getUUID}) but the backend may * actually expect them to be Longs (for database-generated integer * ids). */ Serializable id = session.getModel().unHackStringId(uuid); Node node = session.getNodeById(id); if (node == null) { // required by callers such as AbstractSession.exists throw new NoSuchDocumentException(uuid); } return newDocument(node); } catch (StorageException e) { throw new DocumentException("Failed to get document by UUID", e); } } @Override public Document resolvePath(String path) throws DocumentException { if (path.endsWith("/") && path.length() > 1) { path = path.substring(0, path.length() - 1); } Node node; try { node = session.getNodeByPath(path, session.getRootNode()); } catch (StorageException e) { throw new DocumentException(e); } Document doc = newDocument(node); if (doc == null) { throw new NoSuchDocumentException("No such document: " + path); } return doc; } protected void orderBefore(Node node, Node src, Node dest) throws DocumentException { try { session.orderBefore(node, src, dest); } catch (StorageException e) { throw new DocumentException(e); } } @Override public Document move(Document source, Document parent, String name) throws DocumentException { assert source instanceof SQLDocument; assert parent instanceof SQLDocument; try { if (name == null) { name = source.getName(); } Node result = session.move(((SQLDocument) source).getNode(), ((SQLDocument) parent).getNode(), name); return newDocument(result); } catch (StorageException e) { throw new DocumentException(e); } } private static final Pattern dotDigitsPattern = Pattern.compile("(.*)\\.[0-9]+$"); protected String findFreeName(Node parentNode, String name) throws StorageException { if (session.hasChildNode(parentNode, name, false)) { Matcher m = dotDigitsPattern.matcher(name); if (m.matches()) { // remove trailing dot and digits name = m.group(1); } // add dot + unique digits name += "." + System.currentTimeMillis(); } return name; } @Override public Document copy(Document source, Document parent, String name) throws DocumentException { assert source instanceof SQLDocument; assert parent instanceof SQLDocument; try { if (name == null) { name = source.getName(); } Node parentNode = ((SQLDocument) parent).getNode(); name = findFreeName(parentNode, name); Node copy = session.copy(((SQLDocument) source).getNode(), parentNode, name); return newDocument(copy); } catch (StorageException e) { throw new DocumentException(e); } } @Override public Document getVersion(String versionableId, VersionModel versionModel) throws DocumentException { try { Serializable vid = session.getModel().unHackStringId(versionableId); Node versionNode = session.getVersionByLabel(vid, versionModel.getLabel()); if (versionNode == null) { return null; } versionModel.setDescription(versionNode.getSimpleProperty( Model.VERSION_DESCRIPTION_PROP).getString()); versionModel.setCreated((Calendar) versionNode.getSimpleProperty( Model.VERSION_CREATED_PROP).getValue()); return newDocument(versionNode); } catch (StorageException e) { throw new DocumentException(e); } } @Override public Document createProxy(Document doc, Document folder) throws DocumentException { try { Node folderNode = ((SQLDocument) folder).getNode(); Node targetNode = ((SQLDocument) doc).getNode(); Serializable targetId = targetNode.getId(); Serializable versionableId; if (doc.isVersion()) { versionableId = (Serializable) doc.getProperty( Model.VERSION_VERSIONABLE_PROP).getValue(); } else if (doc.isProxy()) { // copy the proxy targetId = (Serializable) doc.getProperty( Model.PROXY_TARGET_PROP).getValue(); versionableId = (Serializable) doc.getProperty( Model.PROXY_VERSIONABLE_PROP).getValue(); } else { // working copy (live document) versionableId = targetId; } String name = findFreeName(folderNode, doc.getName()); Node proxy = session.addProxy(targetId, versionableId, folderNode, name, null); return newDocument(proxy); } catch (StorageException e) { throw new DocumentException(e); } } @Override public Document createProxyForVersion(Document parent, Document document, String label) throws DocumentException { try { Serializable versionableId = ((SQLDocument) document).getNode().getId(); Node versionNode = session.getVersionByLabel(versionableId, label); if (versionNode == null) { throw new DocumentException("Unknown version: " + label); } Node parentNode = ((SQLDocument) parent).getNode(); String name = findFreeName(parentNode, document.getName()); Node proxy = session.addProxy(versionNode.getId(), versionableId, parentNode, name, null); return newDocument(proxy); } catch (StorageException e) { throw new DocumentException(e); } } @Override public Collection<Document> getProxies(Document document, Document parent) throws DocumentException { Collection<Node> proxyNodes; try { proxyNodes = session.getProxies(((SQLDocument) document).getNode(), parent == null ? null : ((SQLDocument) parent).getNode()); } catch (StorageException e) { throw new DocumentException(e); } List<Document> proxies = new ArrayList<Document>(proxyNodes.size()); for (Node proxyNode : proxyNodes) { proxies.add(newDocument(proxyNode)); } return proxies; } @Override public InputStream getDataStream(String key) throws DocumentException { // XXX TODO throw new UnsupportedOperationException(); } // returned document is r/w even if a version or a proxy, so that normal // props can be set @Override public Document importDocument(String uuid, Document parent, String name, String typeName, Map<String, Serializable> properties) throws DocumentException { assert Model.PROXY_TYPE == CoreSession.IMPORT_PROXY_TYPE; boolean isProxy = typeName.equals(Model.PROXY_TYPE); Map<String, Serializable> props = new HashMap<String, Serializable>(); Long pos = null; // TODO pos if (!isProxy) { // version & live document props.put(Model.MISC_LIFECYCLE_POLICY_PROP, properties.get(CoreSession.IMPORT_LIFECYCLE_POLICY)); props.put(Model.MISC_LIFECYCLE_STATE_PROP, properties.get(CoreSession.IMPORT_LIFECYCLE_STATE)); // compat with old lock import String key = (String) properties.get(CoreSession.IMPORT_LOCK); if (key != null) { String[] values = key.split(":"); if (values.length == 2) { String owner = values[0]; Calendar created = new GregorianCalendar(); try { created.setTimeInMillis(DateFormat.getDateInstance( DateFormat.MEDIUM).parse(values[1]).getTime()); } catch (ParseException e) { // use current date } props.put(Model.LOCK_OWNER_PROP, owner); props.put(Model.LOCK_CREATED_PROP, created); } } props.put(Model.LOCK_OWNER_PROP, properties.get(CoreSession.IMPORT_LOCK_OWNER)); props.put(Model.LOCK_CREATED_PROP, properties.get(CoreSession.IMPORT_LOCK_CREATED)); props.put(Model.MAIN_MAJOR_VERSION_PROP, properties.get(CoreSession.IMPORT_VERSION_MAJOR)); props.put(Model.MAIN_MINOR_VERSION_PROP, properties.get(CoreSession.IMPORT_VERSION_MINOR)); props.put(Model.MAIN_IS_VERSION_PROP, properties.get(CoreSession.IMPORT_IS_VERSION)); } Node parentNode; if (parent == null) { // version parentNode = null; props.put(Model.VERSION_VERSIONABLE_PROP, properties.get(CoreSession.IMPORT_VERSION_VERSIONABLE_ID)); props.put(Model.VERSION_CREATED_PROP, properties.get(CoreSession.IMPORT_VERSION_CREATED)); props.put(Model.VERSION_LABEL_PROP, properties.get(CoreSession.IMPORT_VERSION_LABEL)); props.put(Model.VERSION_DESCRIPTION_PROP, properties.get(CoreSession.IMPORT_VERSION_DESCRIPTION)); props.put(Model.VERSION_IS_LATEST_PROP, properties.get(CoreSession.IMPORT_VERSION_IS_LATEST)); props.put(Model.VERSION_IS_LATEST_MAJOR_PROP, properties.get(CoreSession.IMPORT_VERSION_IS_LATEST_MAJOR)); } else { parentNode = ((SQLDocument) parent).getNode(); if (isProxy) { // proxy props.put(Model.PROXY_TARGET_PROP, properties.get(CoreSession.IMPORT_PROXY_TARGET_ID)); props.put(Model.PROXY_VERSIONABLE_PROP, properties.get(CoreSession.IMPORT_PROXY_VERSIONABLE_ID)); } else { // live document props.put(Model.MAIN_BASE_VERSION_PROP, properties.get(CoreSession.IMPORT_BASE_VERSION_ID)); props.put(Model.MAIN_CHECKED_IN_PROP, properties.get(CoreSession.IMPORT_CHECKED_IN)); } } return importChild(uuid, parentNode, name, pos, typeName, props); } @Override public Query createQuery(String query, Query.Type qType, String... params) throws QueryException { if (qType != Query.Type.NXQL) { throw new UnsupportedQueryTypeException(qType); } if (params != null && params.length != 0) { throw new QueryException("Parameters not supported"); } try { return new SQLSessionQuery(query); } catch (QueryParseException e) { throw new QueryException(e.getMessage() + ": " + query, e); } } @Override public IterableQueryResult queryAndFetch(String query, String queryType, QueryFilter queryFilter, Object... params) throws QueryException { return new SQLSessionQuery(query, queryType).executeAndFetch( queryFilter, params); } protected static final Pattern ORDER_BY_PATH_ASC = Pattern.compile( "(.*)\\s+ORDER\\s+BY\\s+" + NXQL.ECM_PATH + "\\s*$", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); protected static final Pattern ORDER_BY_PATH_DESC = Pattern.compile( "(.*)\\s+ORDER\\s+BY\\s+" + NXQL.ECM_PATH + "\\s+DESC\\s*$", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); protected class SQLSessionQuery implements FilterableQuery { protected final String query; protected final String queryType; public SQLSessionQuery(String query) { this.query = query; queryType = "NXQL"; } public SQLSessionQuery(String query, String queryType) { this.query = query; this.queryType = queryType; } @Override public QueryResult execute() throws QueryException { return execute(QueryFilter.EMPTY, false); } @Override public QueryResult execute(boolean countTotal) throws QueryException { return execute(QueryFilter.EMPTY, countTotal); } @Override public QueryResult execute(QueryFilter queryFilter, boolean countTotal) throws QueryException { try { String query = this.query; // do ORDER BY ecm:path by hand in SQLQueryResult as we can't do // it in SQL (and has to do limit/offset as well) Boolean orderByPath; Matcher matcher = ORDER_BY_PATH_ASC.matcher(query); if (matcher.matches()) { orderByPath = Boolean.TRUE; // ASC } else { matcher = ORDER_BY_PATH_DESC.matcher(query); if (matcher.matches()) { orderByPath = Boolean.FALSE; // DESC } else { orderByPath = null; } } long limit = 0; long offset = 0; if (orderByPath != null) { query = matcher.group(1); limit = queryFilter.getLimit(); offset = queryFilter.getOffset(); queryFilter = QueryFilter.withoutLimitOffset(queryFilter); } PartialList<Serializable> list = session.query(query, queryFilter, countTotal); return new SQLQueryResult(SQLSession.this, list, orderByPath, limit, offset); } catch (StorageException e) { throw new QueryException(e.getMessage(), e); } } public IterableQueryResult executeAndFetch(QueryFilter queryFilter, Object... params) throws QueryException { try { return session.queryAndFetch(query, queryType, queryFilter, params); } catch (StorageException e) { throw new QueryException(e.getMessage(), e); } } } /* * ----- called by SQLDocument ----- */ private Document newDocument(Node node) throws DocumentException { return newDocument(node, true); } // "readonly" meaningful for proxies and versions, used for import private Document newDocument(Node node, boolean readonly) throws DocumentException { if (node == null) { // root's parent return null; } Node targetNode = null; String typeName = node.getPrimaryType(); if (node.isProxy()) { try { Serializable targetId = node.getSimpleProperty( Model.PROXY_TARGET_PROP).getValue(); if (targetId == null) { throw new DocumentException("Proxy has null target"); } targetNode = session.getNodeById(targetId); typeName = targetNode.getPrimaryType(); } catch (StorageException e) { throw new DocumentException(e); } } TypeProvider typeProvider = getTypeManager(); DocumentType type = typeProvider.getDocumentType(typeName); if (type == null) { throw new DocumentException("Unknown document type: " + typeName); } String[] mixins = node.getMixinTypes(); List<CompositeType> mixinTypes = new ArrayList<CompositeType>( mixins.length); for (String mixin : mixins) { CompositeType mixinType = typeProvider.getFacet(mixin); if (mixinType != null) { mixinTypes.add(mixinType); } } if (node.isProxy()) { // proxy seen as a normal document Document proxy = new SQLDocumentLive(node, type, mixinTypes, this, false); Document target = newDocument(targetNode, readonly); return new SQLDocumentProxy(proxy, target); } else if (node.isVersion()) { return new SQLDocumentVersion(node, type, mixinTypes, this, readonly); } else { return new SQLDocumentLive(node, type, mixinTypes, this, false); } } // called by SQLQueryResult iterator & others protected Document getDocumentById(Serializable id) throws DocumentException { try { Node node = session.getNodeById(id); return node == null ? null : newDocument(node); } catch (StorageException e) { throw new DocumentException("Failed to get document: " + id, e); } } // called by SQLQueryResult iterator protected List<Document> getDocumentsById(List<Serializable> ids) throws DocumentException { List<Document> docs = new ArrayList<Document>(ids.size()); try { List<Node> nodes = session.getNodesByIds(ids); for (Node node : nodes) { if (node == null) { continue; } docs.add(newDocument(node)); } } catch (StorageException e) { throw new DocumentException(e.toString(), e); } return docs; } // called by SQLContentProperty protected Binary getBinary(InputStream in) throws DocumentException { try { return session.getBinary(in); } catch (StorageException e) { throw new DocumentException(e); } } /** * Resolves a node given its absolute path, or given an existing node and a * relative path. */ protected Document resolvePath(Node node, String path) throws DocumentException { try { return newDocument(session.getNodeByPath(path, node)); } catch (StorageException e) { throw new DocumentException(e); } } protected Document getParent(Node node) throws DocumentException { try { return newDocument(session.getParentNode(node)); } catch (StorageException e) { throw new DocumentException(e); } } protected String getPath(Node node) throws DocumentException { try { return session.getPath(node); } catch (StorageException e) { throw new DocumentException(e); } } protected Document getChild(Node node, String name) throws DocumentException { Node childNode; try { childNode = session.getChildNode(node, name, false); } catch (StorageException e) { throw new DocumentException(e); } Document doc = newDocument(childNode); if (doc == null) { throw new NoSuchDocumentException("No such document: " + name); } return doc; } // XXX change to iterator? protected List<Document> getChildren(Node node) throws DocumentException { List<Node> nodes; try { nodes = session.getChildren(node, null, false); } catch (StorageException e) { throw new DocumentException(e); } List<Document> children = new ArrayList<Document>(nodes.size()); for (Node n : nodes) { children.add(newDocument(n)); } return children; } protected boolean hasChild(Node node, String name) throws DocumentException { try { return session.hasChildNode(node, name, false); } catch (StorageException e) { throw new DocumentException(e); } } protected boolean hasChildren(Node node) throws DocumentException { try { return session.hasChildren(node, false); } catch (StorageException e) { throw new DocumentException(e); } } protected Document addChild(Node parent, String name, Long pos, String typeName) throws DocumentException { try { return newDocument(session.addChildNode(parent, name, pos, typeName, false)); } catch (StorageException e) { throw new DocumentException(e); } } protected Document importChild(String uuid, Node parent, String name, Long pos, String typeName, Map<String, Serializable> props) throws DocumentException { try { Serializable id = session.getModel().unHackStringId(uuid); Node node = session.addChildNode(id, parent, name, pos, typeName, false); for (Entry<String, Serializable> entry : props.entrySet()) { node.setSimpleProperty(entry.getKey(), entry.getValue()); } return newDocument(node, false); // not readonly } catch (StorageException e) { throw new DocumentException(e); } } protected List<Node> getComplexList(Node node, String name) throws DocumentException { List<Node> nodes; try { nodes = session.getChildren(node, name, true); } catch (StorageException e) { throw new DocumentException(e); } return nodes; } protected void remove(Node node) throws DocumentException { try { session.removeNode(node); } catch (StorageException e) { throw new DocumentException(e); } } protected Document checkIn(Node node, String label, String checkinComment) throws DocumentException { try { Node versionNode = session.checkIn(node, label, checkinComment); return versionNode == null ? null : newDocument(versionNode); } catch (StorageException e) { throw new DocumentException(e); } } protected void checkOut(Node node) throws DocumentException { try { session.checkOut(node); } catch (StorageException e) { throw new DocumentException(e); } } protected void restore(Node node, Node version) throws DocumentException { try { session.restore(node, version); } catch (StorageException e) { throw new DocumentException(e); } } protected Document getVersionByLabel(Serializable versionSeriesId, String label) throws DocumentException { try { Node versionNode = session.getVersionByLabel(versionSeriesId, label); return versionNode == null ? null : newDocument(versionNode); } catch (StorageException e) { throw new DocumentException(e); } } protected List<Document> getVersions(Serializable versionSeriesId) throws DocumentException { List<Node> versionNodes; try { versionNodes = session.getVersions(versionSeriesId); } catch (StorageException e) { throw new DocumentException(e); } List<Document> versions = new ArrayList<Document>(versionNodes.size()); for (Node versionNode : versionNodes) { versions.add(newDocument(versionNode)); } return versions; } public Document getLastVersion(Serializable versionSeriesId) throws DocumentException { try { Node versionNode = session.getLastVersion(versionSeriesId); if (versionNode == null) { return null; } return newDocument(versionNode); } catch (StorageException e) { throw new DocumentException(e); } } protected Node getNodeById(Serializable id) throws DocumentException { try { return session.getNodeById(id); } catch (StorageException e) { throw new DocumentException(e); } } protected Lock getLock(Node node) throws DocumentException { try { return session.getLock(node.getId()); } catch (StorageException e) { throw new DocumentException(e); } } protected Lock setLock(Node node, Lock lock) throws DocumentException { try { return session.setLock(node.getId(), lock); } catch (StorageException e) { throw new DocumentException(e); } } protected Lock removeLock(Node node, String owner) throws DocumentException { try { return session.removeLock(node.getId(), owner, false); } catch (StorageException e) { throw new DocumentException(e); } } /* * ----- property helpers ----- */ protected Property makeACLProperty(Node node) throws DocumentException { CollectionProperty property; try { property = node.getCollectionProperty(Model.ACL_PROP); } catch (StorageException e) { throw new DocumentException(e); } return new SQLCollectionProperty(this, property, null, false); } /** Make a property. */ protected Property makeProperty(Node node, String name, ComplexType parentType, List<CompositeType> mixinTypes, boolean readonly) throws DocumentException { return makeProperties(node, name, parentType, mixinTypes, readonly, 0).get( 0); } /** * Make properties, either a single one, or the whole list for complex list * elements. */ protected List<Property> makeProperties(Node node, String name, Type parentType, List<CompositeType> mixinTypes, boolean readonly, int complexListSize) throws DocumentException { boolean complexList = parentType instanceof ListType; Model model; try { model = session.getModel(); } catch (StorageException e) { throw new DocumentException(e); } Type type = model.getSpecialPropertyType(name); if (type == null) { Field field; if (complexList) { field = ((ListType) parentType).getField(); } else { field = ((ComplexType) parentType).getField(name); if (field == null) { // check mixin types for (CompositeType mixinType : mixinTypes) { field = mixinType.getField(name); if (field != null) { break; } } } if (field == null) { throw new NoSuchPropertyException(name); } // qualify if necessary (some callers pass unprefixed names) name = field.getName().getPrefixedName(); } type = field.getType(); } if (type.isSimpleType()) { SimpleProperty prop; try { prop = node.getSimpleProperty(name); } catch (StorageException e) { throw new DocumentException(e); } Property property = new SQLSimpleProperty(prop, type, readonly); return Collections.singletonList(property); } else if (type.isListType()) { Property property; ListType listType = (ListType) type; if (listType.getFieldType().isSimpleType()) { CollectionProperty prop; try { prop = node.getCollectionProperty(name); } catch (StorageException e) { throw new DocumentException(e); } property = new SQLCollectionProperty(this, prop, listType, readonly); } else { property = new SQLComplexListProperty(node, listType, name, this, readonly); } return Collections.singletonList(property); } else { // complex type, may be part of a complex list or not List<Node> childNodes; try { if (complexList) { if (complexListSize == -1) { // get existing childNodes = session.getChildren(node, name, true); } else { // create with given size (after a remove) childNodes = new ArrayList<Node>(complexListSize); for (int i = 0; i < complexListSize; i++) { Node childNode = session.addChildNode(node, name, Long.valueOf(i), type.getName(), true); childNodes.add(childNode); } } } else { Node childNode = session.getChildNode(node, name, true); if (childNode == null) { // Create the needed complex property. This could also // be done lazily when an actual write is done -- this // would mean refactoring the various SQL*Property // classes to hold parent information. childNode = session.addChildNode(node, name, null, type.getName(), true); } childNodes = Collections.singletonList(childNode); } } catch (StorageException e) { throw new DocumentException(e); } ComplexType complexType = (ComplexType) type; List<Property> properties = new ArrayList<Property>( childNodes.size()); for (Node childNode : childNodes) { Property property; // TODO use a better switch if (TypeConstants.isContentType(type)) { property = new SQLContentProperty(childNode, complexType, this, readonly); } else { property = new SQLComplexProperty(childNode, complexType, this, readonly); } properties.add(property); } return properties; } } /** * This method flag the current session, the read ACLs update will be done * automatically at save time. * */ public void requireReadAclsUpdate() { session.requireReadAclsUpdate(); } }