001/*
002 * (C) Copyright 2006-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 *     Bogdan Stefanescu
018 *     Florent Guillaume
019 */
020
021package org.nuxeo.ecm.core.schema;
022
023import static org.nuxeo.ecm.core.schema.types.ComplexTypeImpl.canonicalXPath;
024
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.LinkedList;
029import java.util.List;
030import java.util.regex.Pattern;
031
032import org.apache.commons.lang.StringUtils;
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.ecm.core.schema.types.ComplexType;
036import org.nuxeo.ecm.core.schema.types.Field;
037import org.nuxeo.ecm.core.schema.types.ListType;
038import org.nuxeo.ecm.core.schema.types.Schema;
039import org.nuxeo.ecm.core.schema.types.Type;
040import org.nuxeo.runtime.api.Framework;
041
042/**
043 * Information about what's to be prefetched: individual properties and whole schemas.
044 */
045public class PrefetchInfo implements Serializable {
046
047    private static final long serialVersionUID = -6495547095819614741L;
048
049    private static final Log log = LogFactory.getLog(PrefetchInfo.class);
050
051    private final String expr;
052
053    private transient String[] fields;
054
055    private transient String[] schemas;
056
057    public PrefetchInfo(String expr) {
058        this.expr = expr;
059    }
060
061    public String[] getSchemas() {
062        parseExpression();
063        return schemas;
064    }
065
066    public String[] getFields() {
067        parseExpression();
068        return fields;
069    }
070
071    private static final Pattern STAR_OR_DIGITS = Pattern.compile("\\*|(\\d+)");
072
073    private void parseExpression() {
074        if (fields != null || expr == null) {
075            return;
076        }
077        SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
078        List<String> fields = new ArrayList<String>();
079        List<String> schemas = new ArrayList<String>();
080
081        for (String s : expr.split("[ \t\n\r,]")) {
082            if (s.isEmpty()) {
083                continue;
084            }
085            s = canonicalXPath(s);
086
087            // maybe a schema?
088            Schema schema = schemaManager.getSchema(s);
089            if (schema != null) {
090                schemas.add(s);
091                continue;
092            }
093
094            // isolate first field of property if complex
095            String[] props = s.split("/");
096            if (props.length == 0) {
097                continue;
098            }
099            String prop = props[0];
100            List<String> complex = Arrays.asList(props).subList(1, props.length);
101
102            // get the field
103            List<String> parts = new LinkedList<String>();
104            Field field;
105            int i = prop.indexOf('.');
106            if (i != -1) {
107                // try schemaName.fieldName
108                String schemaName = prop.substring(0, i);
109                String fieldName = prop.substring(i + 1);
110                schema = schemaManager.getSchema(schemaName);
111                field = schema == null ? null : schema.getField(fieldName);
112            } else {
113                // otherwise try prefixed name
114                field = schemaManager.getField(prop);
115                // TODO must deal with prefix-less props like "size"
116            }
117            if (field == null) {
118                logNotFound(s);
119                continue;
120            }
121            parts.add(field.getName().getPrefixedName());
122
123            // got a field, check its complex properties
124            for (String t : complex) {
125                Type fieldType = field.getType();
126                if (fieldType.isComplexType()) {
127                    // complex type, get subfield
128                    ComplexType fieldComplexType = (ComplexType) fieldType;
129                    field = fieldComplexType.getField(t);
130                    parts.add(t);
131                    continue;
132                } else if (fieldType.isListType()) {
133                    ListType listType = (ListType) fieldType;
134                    if (!listType.getFieldType().isSimpleType()) {
135                        // complex list
136                        // should be * or an integer
137                        if (!STAR_OR_DIGITS.matcher(t).matches()) {
138                            field = null;
139                            break;
140                        }
141                        field = listType.getField();
142                        parts.add("*");
143                    } else {
144                        // array, cannot have subproperties
145                        field = null;
146                        break;
147                    }
148                } else {
149                    // primitive type, cannot have subproperties
150                    field = null;
151                    break;
152                }
153            }
154            if (field == null) {
155                logNotFound(s);
156                continue;
157            }
158            if (!isScalarField(field)) {
159                log.error("Prefetch field '" + s + "' is not scalar");
160                continue;
161            }
162            fields.add(StringUtils.join(parts, '/'));
163        }
164
165        this.fields = fields.toArray(new String[fields.size()]);
166        this.schemas = schemas.toArray(new String[schemas.size()]);
167    }
168
169    /**
170     * Checks if a field is a primitive type or array.
171     */
172    private static boolean isScalarField(Field field) {
173        Type fieldType = field.getType();
174        if (fieldType.isComplexType()) {
175            // complex type
176            return false;
177        }
178        if (!fieldType.isListType()) {
179            // primitive type
180            return true;
181        }
182        // array or complex list?
183        return ((ListType) fieldType).getFieldType().isSimpleType();
184    }
185
186    private static void logNotFound(String s) {
187        log.error("Prefetch field or schema '" + s + "' not found");
188    }
189
190}