/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.usergrid.services;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.apache.usergrid.persistence.Schema;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.commons.lang.StringUtils.capitalize;
import static org.apache.commons.lang.StringUtils.removeEnd;
import static org.apache.commons.lang.StringUtils.split;
import static org.apache.usergrid.utils.InflectionUtils.pluralize;
import static org.apache.usergrid.utils.InflectionUtils.singularize;
import static org.apache.usergrid.utils.StringUtils.stringOrSubstringAfterLast;
import static org.apache.usergrid.utils.StringUtils.stringOrSubstringBeforeFirst;
import static org.apache.usergrid.utils.StringUtils.stringOrSubstringBeforeLast;
public class ServiceInfo {
public static final Charset UTF_8 = Charset.forName( "UTF-8" );
private static final Logger logger = LoggerFactory.getLogger(ServiceInfo.class);
private final String name;
private final boolean rootService;
private final String rootType;
private final String containerType;
private final String collectionName;
private final String itemType;
private final List<String> patterns;
private final List<String> collections;
/** Pre calced since we cache this class */
private int hashCode;
public ServiceInfo( String name, boolean rootService, String rootType, String containerType, String collectionName,
String itemType, List<String> patterns, List<String> collections ) {
this.name = name;
this.rootService = rootService;
this.rootType = rootType;
this.containerType = containerType;
this.collectionName = collectionName;
this.itemType = itemType;
this.patterns = patterns;
this.collections = collections;
Hasher hasher = Hashing.md5().newHasher();
for ( String pattern : patterns ) {
hasher.putString( pattern, UTF_8 );
}
hashCode = hasher.hash().asInt();
}
public static String normalizeServicePattern( String s ) {
if ( s == null ) {
return null;
}
s = s.trim().toLowerCase();
s = removeEnd( s, "/" );
s = removeEnd( s, "/*" );
if ( !s.startsWith( "/" ) ) {
s = "/" + s;
}
return s;
}
public static List<String> getPatterns( String servicePattern ) {
String[] collections = split( servicePattern, "/*/" );
return getPatterns( servicePattern, collections );
}
public static List<String> getPatterns( String servicePattern, String[] collections ) {
if ( collections == null ) {
collections = split( servicePattern, "/*/" );
}
List<String> patterns = new ArrayList<String>();
patterns.add( servicePattern );
if ( servicePattern.indexOf( ':' ) >= 0 ) {
patterns.add( removeTypeSpecifiers( collections ) );
}
String s = getFallbackPattern( collections, 0, collections.length - 1 );
while ( s != null ) {
patterns.add( s );
s = getFallbackPattern( s );
}
return patterns;
}
private static String removeTypeSpecifiers( String[] collections ) {
String s = "";
boolean first = true;
for ( String collection : collections ) {
if ( !first ) {
s += "/*";
}
first = false;
s += "/" + stringOrSubstringBeforeFirst( collection, ':' );
}
return s;
}
private static String getFallbackPattern( String servicePattern ) {
String[] collections = split( servicePattern, "/*/" );
return getFallbackPattern( collections, 0, collections.length - 1 );
}
private static String getFallbackPattern( String[] collections, int first, int last ) {
if ( last < first ) {
return null;
}
if ( ( last - first ) == 1 ) {
if ( !collections[first].startsWith( "entities" ) ) {
return "/entities:" + singularize( collections[first] ) + "/*/" + collections[first + 1];
}
return null;
}
if ( ( last - first ) == 0 ) {
if ( !collections[first].startsWith( "entities" ) ) {
return "/entities:" + singularize( collections[first] );
}
return null;
}
int i = last - 1;
while ( i >= first ) {
if ( collections[i].indexOf( ':' ) > -1 ) {
break;
}
i--;
}
if ( i >= first ) {
String type = stringOrSubstringAfterLast( collections[i], ':' );
String fallback = "/" + pluralize( type );
i++;
while ( i <= last ) {
fallback += "/*/" + collections[i];
i++;
}
return fallback;
}
String eType = determineType( collections, first, last - 1 );
if (!eType.equals("entity")) {
return "/entities:" + eType + "/*/" + collections[last];
}
return "/entities/*/" + collections[last];
}
public static String determineType( String servicePattern ) {
String[] collections = split( servicePattern, '/' );
return determineType( collections, 0, collections.length - 1 );
}
private static String determineType( String[] collections, int first, int last ) {
if ( last < first ) {
return null;
}
if ( first == last ) {
return singularize( stringOrSubstringAfterLast( collections[0], ':' ) );
}
int i = first + 1;
String containerType = singularize( collections[first] );
while ( i <= last ) {
String collectionName = stringOrSubstringBeforeFirst( collections[i], ':' );
String nextType = Schema.getDefaultSchema().getCollectionType( containerType, collectionName );
if ( nextType == null ) {
if ( collections[i].indexOf( ':' ) >= 0 ) {
nextType = stringOrSubstringAfterLast( collections[i], ':' );
}
else if ( ( i < last ) && ( collections[last].indexOf( ':' ) >= 0 ) ) {
nextType = stringOrSubstringAfterLast( collections[last], ':' );
}
else {
return "entity";
}
}
containerType = nextType;
i++;
}
return containerType;
}
/** Hold servicePattern names in a fixed size cache */
private static LoadingCache<String, String> servicePatternCache =
CacheBuilder.newBuilder().maximumSize( 5000 ).build( new CacheLoader<String, String>() {
public String load( String key ) { // no checked exception
return _getClassName( key );
}
} );
/** Delegates to _getClassName via a CacheLoader due to the expense of path name calculation */
public static String getClassName( String servicePattern ) {
try {
return servicePatternCache.get( servicePattern );
}
catch ( ExecutionException ee ) {
logger.error("Error in getClassName for service pattern: {}", servicePattern, ee);
}
return _getClassName( servicePattern );
}
private static String _getClassName( String servicePattern ) {
servicePattern = normalizeServicePattern( servicePattern );
String[] collections = split( servicePattern, "/*/" );
if ( collections[0].startsWith( "entities" ) ) {
if ( collections.length == 1 ) {
return "generic.RootCollectionService";
}
if ( collections[0].indexOf( ':' ) < 0 ) {
return "generic.GenericConnectionsService";
}
String container = stringOrSubstringAfterLast( collections[0], ':' );
String collectionName = stringOrSubstringBeforeFirst( collections[1], ':' );
if ( Schema.getDefaultSchema().hasCollection( container, collectionName ) ) {
return "generic.GenericCollectionService";
}
return "generic.GenericConnectionsService";
}
String packages = "";
String types = "";
if ( collections.length == 1 ) {
packages = stringOrSubstringBeforeLast( stringOrSubstringBeforeFirst( collections[0], ':' ), '.' ) + ".";
}
else {
for ( int i = 0; i < collections.length; i++ ) {
if ( i == 0 ) {
packages = stringOrSubstringBeforeFirst( collections[i], ':' ) + ".";
}
else {
packages += stringOrSubstringBeforeLast( stringOrSubstringBeforeFirst( collections[i], ':' ), '.' )
+ ".";
}
if ( ( i < ( collections.length - 1 ) ) && ( collections[i].indexOf( ':' ) >= 0 ) ) {
types += capitalize(
stringOrSubstringAfterLast( stringOrSubstringAfterLast( collections[i], ':' ), '.' ) );
}
}
}
return packages + types + capitalize(
stringOrSubstringAfterLast( stringOrSubstringBeforeFirst( collections[collections.length - 1], ':' ),
'.' ) ) + "Service";
}
private static final Map<String, ServiceInfo> serviceInfoCache = new LinkedHashMap<String, ServiceInfo>();
public static ServiceInfo getServiceInfo( String servicePattern ) {
if ( servicePattern == null ) {
return null;
}
servicePattern = normalizeServicePattern( servicePattern );
ServiceInfo info = serviceInfoCache.get( servicePattern );
if ( info != null ) {
return info;
}
String[] collections = split( servicePattern, "/*/" );
if ( collections.length == 0 ) {
return null;
}
String collectionName = stringOrSubstringBeforeFirst( collections[collections.length - 1], ':' );
if ( collectionName == null ) {
throw new NullPointerException( "Collection name is null" );
}
String ownerType = "entity";
String rootType = determineType( collections, 0, 0 );
if ( collections.length == 1 ) {
ownerType = "application";
}
if ( collections.length > 1 ) {
ownerType = determineType( collections, 0, collections.length - 2 );
}
String itemType = determineType( collections, 0, collections.length - 1 );
List<String> patterns = getPatterns( servicePattern, collections );
info = new ServiceInfo( servicePattern, collections.length == 1, rootType, ownerType, collectionName, itemType,
patterns, Arrays.asList( collections ) );
serviceInfoCache.put( servicePattern, info );
return info;
}
public String getClassName() {
return getClassName( name );
}
public String getName() {
return name;
}
public boolean isRootService() {
return rootService;
}
public String getRootType() {
return rootType;
}
public boolean isGenericRootType() {
return ( "entity".equals( rootType ) ) || ( "entities".equals( rootType ) );
}
public String getContainerType() {
return containerType;
}
public boolean isContainerType() {
return ( "entity".equals( containerType ) ) || ( "entities".equals( containerType ) );
}
public String getCollectionName() {
return collectionName;
}
public String getItemType() {
return itemType;
}
public boolean isGenericItemType() {
return "entity".equals( itemType );
}
public List<String> getPatterns() {
return patterns;
}
public List<String> getCollections() {
return collections;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals( Object obj ) {
if ( obj instanceof ServiceInfo ) {
return hashCode == ( ( ServiceInfo ) obj ).hashCode;
}
return false;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return hashCode;
}
}