/* * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.buildServer.server.rest.model; import com.intellij.openapi.util.text.StringUtil; import java.util.HashMap; import java.util.Map; import jetbrains.buildServer.server.rest.data.Locator; import jetbrains.buildServer.server.rest.errors.BadRequestException; import jetbrains.buildServer.server.rest.errors.LocatorProcessException; import jetbrains.buildServer.serverSide.TeamCityProperties; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * @author Yegor.Yarko * Date: 28.11.13 */ public class Fields { private static final String NONE_FIELDS_PATTERN = "-"; private static final String DEFAULT_FIELDS_SHORT_PATTERN = ""; private static final String DEFAULT_FIELDS_SHORT_PATTERN_ALTERNATIVE = "$short"; private static final String DEFAULT_FIELDS_LONG_PATTERN = "$long"; private static final String ALL_FIELDS_PATTERN = "*"; private static final String ALL_NESTED_FIELDS_PATTERN = "**"; private static final String OPTIONAL_FIELDS_PATTERN = "$optional"; private static final String LOCATOR_CUSTOM_NAME = "$locator"; public static final Fields NONE = new Fields(NONE_FIELDS_PATTERN, null, true); // no fields at all public static final Fields SHORT = new Fields(DEFAULT_FIELDS_SHORT_PATTERN, null, true); // short (reference) form. Uses short or none form for the fields. public static final Fields ALL = new Fields(ALL_FIELDS_PATTERN, null, true); // all fields are present and are in the short form public static final Fields LONG = new Fields(DEFAULT_FIELDS_LONG_PATTERN, null, true); // long form. Uses long, short or none form for the fields. Generally fields with default values are not included. public static final Fields ALL_NESTED = new Fields(ALL_NESTED_FIELDS_PATTERN, null, true); // maximum, all fields are included in the same maximum form @NotNull private final String myFieldsSpec; private Locator myFieldsSpecLocator; @NotNull private final Map myRestrictedFields; private Fields(@NotNull String actualFieldsSpec, @Nullable Map restrictedFields, boolean isInternal) { myFieldsSpec = actualFieldsSpec; myRestrictedFields = restrictedFields != null ? new HashMap(restrictedFields) : new HashMap(); } public Fields (@Nullable String fieldsSpec){ this(fieldsSpec, getDefaultFields()); } public Fields(@Nullable String fieldsSpec, @NotNull Fields defaultFields) { this(fieldsSpec != null ? fieldsSpec : defaultFields.myFieldsSpec, null, true); } private static Fields getDefaultFields() { final String defaultFieldsProperty = TeamCityProperties.getPropertyOrNull("rest.beans.fields.default"); if (defaultFieldsProperty == null){ return LONG; }else{ return new Fields(defaultFieldsProperty, null, true); } } public boolean isMoreThenShort() { return !isShort() && !isNone(); } public boolean isShort() { return DEFAULT_FIELDS_SHORT_PATTERN.equals(myFieldsSpec) || getCustomDimension(DEFAULT_FIELDS_SHORT_PATTERN_ALTERNATIVE) != null; } public boolean isAll() { return getCustomDimension(ALL_FIELDS_PATTERN) != null; } public boolean isLong() { return getCustomDimension(DEFAULT_FIELDS_LONG_PATTERN) != null; } public boolean isAllNested() { return getCustomDimension(ALL_NESTED_FIELDS_PATTERN) != null; } public boolean isNone() { return getCustomDimension(NONE_FIELDS_PATTERN) != null; } /** * * @param fieldName * @return null if the defaults should be used */ @Nullable public Boolean isIncluded(@NotNull final String fieldName){ return isIncluded(fieldName, null, null); } @Nullable public Boolean isIncluded(@NotNull final String fieldName, @Nullable final Boolean defaultForShort) { return isIncluded(fieldName, defaultForShort, null); } @Nullable public Boolean isIncludedOr(@NotNull final String[] fieldNames, @Nullable final Boolean defaultForShort, @Nullable final Boolean defaultForLong) { Boolean result = false; for (String fieldName : fieldNames) { final Boolean included = isIncluded(fieldName, defaultForShort, defaultForLong); if (included != null) { if (included) { return true; } } else { result = null; } } return result; } @Nullable @Contract("_, !null, !null -> !null") public Boolean isIncluded(@NotNull final String fieldName, @Nullable final Boolean defaultForShort, @Nullable final Boolean defaultForLong) { return isIncluded(fieldName, null, defaultForShort, defaultForLong); } @Nullable @Contract("_, _, !null, !null -> !null") public Boolean isIncluded(@NotNull final String fieldName, @Nullable final Boolean isCashed, @Nullable final Boolean defaultForShort, @Nullable final Boolean defaultForLong) { final String fieldSpec = getCustomDimension(fieldName); if (fieldSpec != null) { if (NONE_FIELDS_PATTERN.equals(fieldSpec)) { return false; } if (OPTIONAL_FIELDS_PATTERN.equals(fieldSpec) || new Fields(fieldSpec).getCustomDimension(OPTIONAL_FIELDS_PATTERN) != null) { if (isCashed == null && TeamCityProperties.getBoolean("rest.beans.fields.optional.errorIfUsedForNotSupportedField")) { throw new BadRequestException("Special fields pattern \"" + OPTIONAL_FIELDS_PATTERN + "\" is not supported for field \"" + fieldName + "\""); } return isCashed != null && isCashed; } return true; } if (isNone()){ return false; } final Fields restrictedFields = myRestrictedFields.get(fieldName); if (restrictedFields != null) { if (!restrictedFields.isIncluded(fieldName, true, true)) { return false; } } if (isAllNested() || isAll()) { return true; } if (isShort()) { return defaultForShort; } if (isLong()) { return defaultForLong; } return false; } @NotNull public Fields getNestedField(@NotNull final String nestedFieldName) { return getNestedField(nestedFieldName, NONE, SHORT); } /** * Returnes fields for the nested field 'nestedFieldName' defaulting to 'defaultForShort' and 'defaultForLong' for corresponding presentations. * Excludes stored in 'default*Presentation' paramters are ignored. * @param nestedFieldName * @param defaultForShort - default to use if the current Fields is short presentation * @param defaultForLong - default to use if the current Fields is long presentation * @return */ @NotNull public Fields getNestedField(@NotNull final String nestedFieldName, @NotNull final Fields defaultForShort, @NotNull final Fields defaultForLong) { final Boolean included = isIncluded(nestedFieldName); if (included != null && !included) { return NONE; } Fields restrictedField = myRestrictedFields.get(nestedFieldName); if (restrictedField == null) { restrictedField = ALL_NESTED; } final Map newRestrictedFields = new HashMap(myRestrictedFields); newRestrictedFields.put(nestedFieldName, SHORT); final String fieldSpec = getCustomDimension(nestedFieldName); if(fieldSpec != null){ return new Fields(fieldSpec, newRestrictedFields, true); } if (isAllNested()) { return new Fields(minPattern(restrictedField.myFieldsSpec, ALL_NESTED_FIELDS_PATTERN), newRestrictedFields, true); } if (isLong()) { return new Fields(minPattern(restrictedField.myFieldsSpec, defaultForLong.myFieldsSpec), newRestrictedFields, true); } if (isAll()) { return new Fields(minPattern(restrictedField.myFieldsSpec, DEFAULT_FIELDS_SHORT_PATTERN), newRestrictedFields, true); } if (isShort()) { newRestrictedFields.put(nestedFieldName, NONE); return new Fields(minPattern(restrictedField.myFieldsSpec, defaultForShort.myFieldsSpec), newRestrictedFields, true); } return new Fields(NONE_FIELDS_PATTERN, newRestrictedFields, true); } @Nullable public String getCustomDimension(@NotNull final String fieldName) { final Locator parsedCustomFields = getParsedCustomFields(); return parsedCustomFields == null ? null : parsedCustomFields.getSingleDimensionValue(fieldName); } @Nullable public String getLocator() { return getCustomDimension(LOCATOR_CUSTOM_NAME); } private static String minPattern(final String a, final String b) { if (ALL_NESTED_FIELDS_PATTERN.equals(a)) { return b; } if (DEFAULT_FIELDS_LONG_PATTERN.equals(a)) { if (ALL_NESTED_FIELDS_PATTERN.equals(b)) return a; return b; } if (ALL_FIELDS_PATTERN.equals(a)) { if (ALL_NESTED_FIELDS_PATTERN.equals(b)) return a; if (DEFAULT_FIELDS_LONG_PATTERN.equals(b)) return a; return b; } if (DEFAULT_FIELDS_SHORT_PATTERN.equals(a) || DEFAULT_FIELDS_SHORT_PATTERN_ALTERNATIVE.equals(a)) { if (ALL_NESTED_FIELDS_PATTERN.equals(b)) return a; if (DEFAULT_FIELDS_LONG_PATTERN.equals(b)) return a; if (ALL_FIELDS_PATTERN.equals(b)) return a; return b; } if (NONE_FIELDS_PATTERN.equals(a)) return NONE_FIELDS_PATTERN; throw new BadRequestException("Comparing non standard Fields is not supported: a='" +a + "', b='" + b + "'"); } @NotNull public Fields increaseRestrictedField(@NotNull final String fieldName, @NotNull Fields newRestriction) { final Fields currentRestriction = myRestrictedFields.get(fieldName); if (minPattern(currentRestriction.myFieldsSpec, newRestriction.myFieldsSpec).equals(currentRestriction.myFieldsSpec)) { return resetRestrictedField(fieldName, newRestriction); } return currentRestriction; } @NotNull public Fields resetRestrictedField(@NotNull final String fieldName, @NotNull Fields newRestriction) { final Map newRestrictedFields = new HashMap(myRestrictedFields); newRestrictedFields.put(fieldName, newRestriction); return new Fields(myFieldsSpec, newRestrictedFields, true); } @NotNull public Fields removeRestrictedField(@NotNull final String fieldName) { final Map newRestrictedFields = new HashMap(myRestrictedFields); newRestrictedFields.remove(fieldName); return new Fields(myFieldsSpec, newRestrictedFields, true); } @Nullable Locator getParsedCustomFields() { if (myFieldsSpecLocator == null && !StringUtil.isEmpty(myFieldsSpec)) { try { myFieldsSpecLocator = new Locator(myFieldsSpec, true, NONE_FIELDS_PATTERN, DEFAULT_FIELDS_SHORT_PATTERN_ALTERNATIVE, DEFAULT_FIELDS_LONG_PATTERN, ALL_FIELDS_PATTERN, ALL_NESTED_FIELDS_PATTERN, LOCATOR_CUSTOM_NAME, OPTIONAL_FIELDS_PATTERN); myFieldsSpecLocator.addHiddenDimensions(OPTIONAL_FIELDS_PATTERN); } catch (LocatorProcessException e) { throw new LocatorProcessException("Error parsing fields specification: " + e.getMessage(), e); } } return myFieldsSpecLocator; } @NotNull public String getFieldsSpec() { return myFieldsSpec; } @Override public String toString() { return getFieldsSpec(); } }