001/*
002 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019package org.nuxeo.ecm.core.schema;
020
021import static org.apache.commons.lang.ObjectUtils.NULL;
022import static org.nuxeo.ecm.core.schema.types.ComplexTypeImpl.canonicalXPath;
023
024import java.io.Serializable;
025import java.util.HashMap;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029
030import org.nuxeo.ecm.core.schema.types.Schema;
031import org.nuxeo.runtime.api.Framework;
032
033/**
034 * Information about what's to be prefetched: individual properties and whole schemas.
035 */
036public class Prefetch implements Serializable {
037
038    private static final long serialVersionUID = 1L;
039
040    /**
041     * map of prefix:name -> value
042     * <p>
043     * key can be a canonical xpath like prefix:name/0/othername
044     * <p>
045     * null values are stored as actual nulls
046     */
047    public Map<String, Serializable> values;
048
049    /**
050     * map of schema -> list of prefix:name
051     */
052    public Map<String, List<String>> keysBySchema;
053
054    /**
055     * map of schema -> name -> prefix:name
056     */
057    public Map<String, Map<String, String>> keysBySchemaAndName;
058
059    public Prefetch() {
060        values = new HashMap<String, Serializable>();
061        keysBySchema = new HashMap<String, List<String>>();
062        keysBySchemaAndName = new HashMap<String, Map<String, String>>();
063    }
064
065    public boolean isEmpty() {
066        return values.isEmpty();
067    }
068
069    public void put(String prefixedName, String schemaName, String name, Serializable value) {
070        values.put(prefixedName, value);
071        if (schemaName != null) {
072            Map<String, String> keysByName = keysBySchemaAndName.get(schemaName);
073            if (keysByName == null) {
074                keysBySchemaAndName.put(schemaName, keysByName = new HashMap<String, String>());
075            }
076            keysByName.put(name, prefixedName);
077            List<String> keys = keysBySchema.get(schemaName);
078            if (keys == null) {
079                keysBySchema.put(schemaName, keys = new LinkedList<String>());
080            }
081            keys.add(prefixedName);
082        }
083    }
084
085    public Serializable get(String xpath) {
086        xpath = canonicalXPath(xpath);
087        if (values.containsKey(xpath)) {
088            return cloned(values.get(xpath));
089        }
090        return NULL;
091    }
092
093    public Serializable get(String schemaName, String name) {
094        Map<String, String> keysByName = keysBySchemaAndName.get(schemaName);
095        if (keysByName != null) {
096            String prefixedName = keysByName.get(name);
097            if (prefixedName != null && values.containsKey(prefixedName)) {
098                return cloned(values.get(prefixedName));
099            }
100        }
101        return NULL;
102    }
103
104    // make sure we return a new array
105    protected Serializable cloned(Serializable value) {
106        if (value instanceof Object[]) {
107            value = ((Object[]) value).clone();
108        }
109        return value;
110    }
111
112    public boolean isPrefetched(String xpath) {
113        xpath = canonicalXPath(xpath);
114        return values.containsKey(xpath);
115    }
116
117    public boolean isPrefetched(String schemaName, String name) {
118        Map<String, String> keysByName = keysBySchemaAndName.get(schemaName);
119        if (keysByName == null) {
120            return false;
121        }
122        String prefixedName = keysByName.get(name);
123        if (prefixedName == null) {
124            return false;
125        }
126        return values.containsKey(prefixedName);
127    }
128
129    /**
130     * Clears the prefetches for a given schema.
131     */
132    public void clearPrefetch(String schemaName) {
133        keysBySchemaAndName.remove(schemaName);
134        List<String> keys = keysBySchema.remove(schemaName);
135        if (keys != null) {
136            for (String prefixedName : keys) {
137                values.remove(prefixedName);
138            }
139        }
140    }
141
142    /**
143     * Gets the schema name for a given xpath.
144     * <p>
145     * The type is used to resolve non-prefixed properties.
146     *
147     * @return the schema name or {@code null}
148     */
149    public String getXPathSchema(String xpath, DocumentType type) {
150        xpath = canonicalXPath(xpath);
151        int i = xpath.indexOf('/');
152        String prop = i == -1 ? xpath : xpath.substring(0, i);
153        int p = prop.indexOf(':');
154        if (p == -1) {
155            for (Schema schema : type.getSchemas()) {
156                if (schema.hasField(prop)) {
157                    return schema.getName();
158                }
159            }
160            return null;
161        } else {
162            String prefix = prop.substring(0, p);
163            SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
164            Schema schema = schemaManager.getSchemaFromPrefix(prefix);
165            if (schema == null) {
166                schema = schemaManager.getSchema(prefix);
167            }
168            return schema == null ? null : schema.getName();
169        }
170    }
171
172}