| /* |
| * 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.openjpa.meta; |
| |
| import java.io.Serializable; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.apache.openjpa.conf.OpenJPAConfiguration; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.lib.util.StringDistance; |
| import org.apache.openjpa.lib.util.StringUtil; |
| |
| |
| /** |
| * Vendor extensions. This class is thread safe for reads, but not for |
| * mutations. |
| * |
| * @author Abe White |
| */ |
| public abstract class Extensions |
| implements Serializable { |
| |
| |
| private static final long serialVersionUID = 1L; |
| |
| public static final String OPENJPA = "openjpa"; |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (Extensions.class); |
| |
| private Map _exts = null; |
| private Map _embed = null; |
| |
| /** |
| * Return true if there are no keys for any vendor. |
| */ |
| public boolean isEmpty() { |
| return (_exts == null || _exts.isEmpty()) |
| && (_embed == null || _embed.isEmpty()); |
| } |
| |
| /** |
| * Return all vendors who have extension keys at this level. |
| */ |
| public String[] getExtensionVendors() { |
| if (_exts == null || _exts.isEmpty()) |
| return new String[0]; |
| |
| Set vendors = new TreeSet(); |
| for (Object o : _exts.keySet()) { |
| vendors.add(getVendor(o)); |
| } |
| return (String[]) vendors.toArray(new String[vendors.size()]); |
| } |
| |
| /** |
| * Return all extension keys. |
| */ |
| public String[] getExtensionKeys() { |
| return getExtensionKeys(OPENJPA); |
| } |
| |
| /** |
| * Return all extension keys for the given vendor. |
| */ |
| public String[] getExtensionKeys(String vendor) { |
| if (_exts == null || _exts.isEmpty()) |
| return new String[0]; |
| |
| Collection keys = new TreeSet(); |
| Object key; |
| for (Object o : _exts.keySet()) { |
| key = o; |
| if (vendor.equals(getVendor(key))) |
| keys.add(getKey(key)); |
| } |
| return (String[]) keys.toArray(new String[keys.size()]); |
| } |
| |
| /** |
| * Return true if the extension with the given key exists. |
| */ |
| public boolean hasExtension(String key) { |
| return hasExtension(OPENJPA, key); |
| } |
| |
| /** |
| * Return true if the extension with the given key exists. |
| */ |
| public boolean hasExtension(String vendor, String key) { |
| return _exts != null && _exts.containsKey(getHashKey(vendor, key)); |
| } |
| |
| /** |
| * Add a vendor extension to this entity. |
| */ |
| public void addExtension(String key, Object value) { |
| addExtension(OPENJPA, key, value); |
| } |
| |
| /** |
| * Add a vendor extension to this entity. |
| */ |
| public void addExtension(String vendor, String key, Object value) { |
| if (_exts == null) |
| _exts = new HashMap(); |
| _exts.put(getHashKey(vendor, key), value); |
| } |
| |
| /** |
| * Remove a vendor extension. |
| */ |
| public boolean removeExtension(String key) { |
| return removeExtension(OPENJPA, key); |
| } |
| |
| /** |
| * Remove a vendor extension. |
| */ |
| public boolean removeExtension(String vendor, String key) { |
| if (_exts != null && _exts.remove(getHashKey(vendor, key)) != null) { |
| removeEmbeddedExtensions(key); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Get the value of an extension. |
| */ |
| public Object getObjectExtension(String key) { |
| return getObjectExtension(OPENJPA, key); |
| } |
| |
| /** |
| * Get the value of an extension. |
| */ |
| public Object getObjectExtension(String vendor, String key) { |
| if (_exts == null) |
| return null; |
| return _exts.get(getHashKey(vendor, key)); |
| } |
| |
| /** |
| * Get the value as a string. |
| */ |
| public String getStringExtension(String key) { |
| return getStringExtension(OPENJPA, key); |
| } |
| |
| /** |
| * Get the value as a string. |
| */ |
| public String getStringExtension(String vendor, String key) { |
| Object val = getObjectExtension(vendor, key); |
| return (val == null) ? null : val.toString(); |
| } |
| |
| /** |
| * Get the value as an int. |
| */ |
| public int getIntExtension(String key) { |
| return getIntExtension(OPENJPA, key); |
| } |
| |
| /** |
| * Get the value as an int. |
| */ |
| public int getIntExtension(String vendor, String key) { |
| String str = getStringExtension(vendor, key); |
| return (str == null) ? 0 : Integer.parseInt(str); |
| } |
| |
| /** |
| * Get the value as a double. |
| */ |
| public double getDoubleExtension(String key) { |
| return getDoubleExtension(OPENJPA, key); |
| } |
| |
| /** |
| * Get the value as a double. |
| */ |
| public double getDoubleExtension(String vendor, String key) { |
| String str = getStringExtension(vendor, key); |
| return (str == null) ? 0D : Double.parseDouble(str); |
| } |
| |
| /** |
| * Get the value as a boolean. |
| */ |
| public boolean getBooleanExtension(String key) { |
| return getBooleanExtension(OPENJPA, key); |
| } |
| |
| /** |
| * Get the value as a boolean. |
| */ |
| public boolean getBooleanExtension(String vendor, String key) { |
| String str = getStringExtension(vendor, key); |
| return (str == null) ? false : Boolean.valueOf(str); |
| } |
| |
| /** |
| * Return the embedded extensions under the given key. |
| */ |
| public Extensions getEmbeddedExtensions(String key, boolean create) { |
| return getEmbeddedExtensions(OPENJPA, key, create); |
| } |
| |
| /** |
| * Return the embedded extensions under the given key. |
| */ |
| public Extensions getEmbeddedExtensions(String vendor, String key, |
| boolean create) { |
| if (_embed == null && !create) |
| return null; |
| if (_embed == null) |
| _embed = new HashMap(); |
| |
| Object hk = getHashKey(vendor, key); |
| Extensions exts = (Extensions) _embed.get(hk); |
| if (exts == null && !create) |
| return null; |
| if (exts == null) { |
| exts = new EmbeddedExtensions(this); |
| _embed.put(hk, exts); |
| |
| // required to recognize embedded extensions without values |
| if (_exts == null) |
| _exts = new HashMap(); |
| if (!_exts.containsKey(hk)) |
| _exts.put(hk, null); |
| } |
| return exts; |
| } |
| |
| public boolean removeEmbeddedExtensions(String key) { |
| return removeEmbeddedExtensions(OPENJPA, key); |
| } |
| |
| public boolean removeEmbeddedExtensions(String vendor, String key) { |
| return _embed != null |
| && _embed.remove(getHashKey(vendor, key)) != null; |
| } |
| |
| /** |
| * Copy the extensions not present in this instance but present in the |
| * given instance. |
| */ |
| protected void copy(Extensions exts) { |
| if (exts.isEmpty()) |
| return; |
| |
| if (exts._exts != null && !exts._exts.isEmpty()) { |
| if (_exts == null) |
| _exts = new HashMap(); |
| |
| Map.Entry entry; |
| for (Object o : exts._exts.entrySet()) { |
| entry = (Map.Entry) o; |
| if (!_exts.containsKey(entry.getKey())) |
| _exts.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| if (exts._embed != null && !exts._embed.isEmpty()) { |
| if (_embed == null) |
| _embed = new HashMap(); |
| |
| Map.Entry entry; |
| Extensions embedded; |
| for (Object o : exts._embed.entrySet()) { |
| entry = (Map.Entry) o; |
| embedded = (Extensions) _embed.get(entry.getKey()); |
| if (embedded == null) { |
| embedded = new EmbeddedExtensions(this); |
| _embed.put(entry.getKey(), embedded); |
| } |
| embedded.copy((Extensions) entry.getValue()); |
| } |
| } |
| } |
| |
| /** |
| * Helper method to issue warnings for any extensions that we |
| * recognize but do not use. |
| * |
| * @since 0.3.1.3 |
| */ |
| public void validateExtensionKeys() { |
| if (_exts == null || _exts.isEmpty()) |
| return; |
| |
| OpenJPAConfiguration conf = getRepository().getConfiguration(); |
| Log log = conf.getLog(OpenJPAConfiguration.LOG_METADATA); |
| if (!log.isWarnEnabled()) |
| return; |
| |
| Collection validNames = new TreeSet(); |
| addExtensionKeys(validNames); |
| |
| // this is where we store things like "jdbc-" for a |
| // prefix for an extension name that we won't validate; that |
| // way a new vendor could theoretically add in their |
| // own prefix into the localizer.properties file and |
| // not have to issue warnings for their extensions |
| String prefixes = _loc.get("extension-datastore-prefix").getMessage(); |
| String[] allowedPrefixes = null; |
| if (prefixes != null) |
| allowedPrefixes = StringUtil.split(prefixes, ",", 0); |
| |
| Object next; |
| String key; |
| outer: |
| for (Object o : _exts.keySet()) { |
| next = o; |
| if (!OPENJPA.equals(getVendor(next))) |
| continue; |
| key = getKey(next); |
| if (validNames.contains(key)) |
| continue; |
| |
| if (allowedPrefixes != null) { |
| for (String allowedPrefix : allowedPrefixes) { |
| if (key.startsWith(allowedPrefix) |
| && !validateDataStoreExtensionPrefix |
| (allowedPrefix)) |
| continue outer; |
| } |
| } |
| |
| // try to determine if there are any other names that are |
| // similiar to this one, so we can add in a hint |
| String closestName = StringDistance.getClosestLevenshteinDistance |
| (key, validNames, 0.5f); |
| |
| if (closestName == null) |
| log.warn(_loc.get("unrecognized-extension", this, |
| key, validNames)); |
| else |
| log.warn(_loc.get("unrecognized-extension-hint", |
| new Object[]{this, key, validNames, closestName})); |
| } |
| } |
| |
| /** |
| * Add all the known extension keys to the specified collection; any |
| * implementation that utilized new extensions should override this |
| * method to include both the known extensions of its superclass as well |
| * as its own extension keys. |
| * |
| * @since 0.3.1.3 |
| */ |
| protected void addExtensionKeys(Collection exts) { |
| // no extensions by default |
| } |
| |
| /** |
| * Return true if extensions starting with the given official datastore |
| * prefix should be validated for this runtime. |
| */ |
| protected boolean validateDataStoreExtensionPrefix(String prefix) { |
| return false; |
| } |
| |
| /** |
| * Return the metadata repository. |
| */ |
| public abstract MetaDataRepository getRepository(); |
| |
| /** |
| * Create a hash key for the given vendor/key combo. |
| */ |
| private Object getHashKey(String vendor, String key) { |
| if (OPENJPA.equals(vendor)) |
| return key; |
| return new HashKey(vendor, key); |
| } |
| |
| /** |
| * Extract the vendor from the given hash key. |
| */ |
| private String getVendor(Object hashKey) { |
| return (hashKey instanceof String) ? OPENJPA : |
| ((HashKey) hashKey).vendor; |
| } |
| |
| /** |
| * Extract the key from the given hash key. |
| */ |
| private String getKey(Object hashKey) { |
| return (hashKey instanceof String) ? (String) hashKey |
| : ((HashKey) hashKey).key; |
| } |
| |
| /** |
| * Key class. |
| */ |
| private static class HashKey |
| implements Serializable { |
| |
| |
| private static final long serialVersionUID = 1L; |
| public final String vendor; |
| public final String key; |
| |
| public HashKey(String vendor, String key) { |
| this.vendor = vendor; |
| this.key = key; |
| } |
| |
| @Override |
| public int hashCode() { |
| int i = 0; |
| if (vendor != null) |
| i = vendor.hashCode(); |
| if (key != null) |
| i += 17 * key.hashCode(); |
| return i; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (other == this) |
| return true; |
| HashKey hk = (HashKey) other; |
| return Objects.equals(vendor, hk.vendor) |
| && Objects.equals(key, hk.key); |
| } |
| } |
| |
| /** |
| * Embedded extensions implementation. |
| */ |
| private static class EmbeddedExtensions |
| extends Extensions { |
| |
| |
| private static final long serialVersionUID = 1L; |
| private final Extensions _parent; |
| |
| public EmbeddedExtensions(Extensions parent) { |
| _parent = parent; |
| } |
| |
| @Override |
| public MetaDataRepository getRepository () |
| { |
| return _parent.getRepository (); |
| } |
| } |
| } |