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