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