blob: 33e78a56b5d9845d1ee6d105f8f7b2c2456357ad [file] [log] [blame]
/*
* 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.datacache;
import java.lang.reflect.Array;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.openjpa.lib.conf.PluginListValue;
import org.apache.openjpa.lib.conf.Value;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.UserException;
/**
* A partitioned data cache maintains a set of partitions that are DataCache themselves.
* Each of the partitioned DataCaches can be individually configured.
* However, all partitions must be of the same type. By default, this cache uses
* {@linkplain ConcurrentDataCache} as its partitions.
* <br>
* This cache can be configured as a plug-in as follows:
* <br>
* <code>&lt;property name='openjpa.DataCache"
* value="partitioned(name=X, PartitionType=concurrent,Partitions='(name=a,cacheSize=100),
* (name=b,cacheSize=200)')</code>
* <br>
* Notice that the individual partition properties are enclosed parentheses, separated by comma
* and finally the whole property string is enclosed in single quote.
* Each partition must have a non-empty name that are unique among the partitions.
* The {@linkplain CacheDistributionPolicy policy} can return
* the name of a partition to distribute the managed instances to be cached in respective partition.
*
* The above configuration will configure a partitioned cache named <code>X</code> with two partitions named
* <code>a</code> and <code>b</code> with cache size <code>100</code> and <code>200</code> respectively.
* Besides the two partitions, this cache instance itself can store data and referred by its own name
* (<code>X</code> in the above example).
* <br>
*
* @author Pinaki Poddar
*
* @since 2.0.0
*/
public class PartitionedDataCache extends ConcurrentDataCache {
private static final long serialVersionUID = 1L;
private static final Localizer _loc = Localizer.forPackage(PartitionedDataCache.class);
private Class<? extends DataCache> _type = ConcurrentDataCache.class;
private final List<String> _partProperties = new ArrayList<>();
private final Map<String, DataCache> _partitions = new HashMap<>();
@Override
public void initialize(DataCacheManager mgr) {
super.initialize(mgr);
for(DataCache part : _partitions.values()){
part.initialize(mgr);
}
}
/**
* Sets the type of the partitions.
* Each partition is a DataCache itself.
*
* @param type the name of the type that implements {@linkplain DataCache} interface.
* Aliases such as <code>"concurrent"</code> is also recognized.
*
* @throws Exception if the given type is not resolvable to a loadable type.
*/
public void setPartitionType(String type) throws Exception {
Value value = conf.getValue("DataCache");
ClassLoader ctxLoader = AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction());
ClassLoader loader = conf.getClassResolverInstance().getClassLoader(null, ctxLoader);
_type = (Class<? extends DataCache>) AccessController.doPrivileged(
J2DoPrivHelper.getForNameAction(value.unalias(type), true, loader));
}
/**
* Set partitions from a String configuration.
*
* @param parts a String of the form <code>(p1, p2, p3)</code> where p1, p2 etc. itself are plug-in strings
* for individual Data Cache configuration.
*/
public void setPartitions(String parts) {
_partProperties.clear();
parsePartitionProperties(parts);
PluginListValue partitions = new PluginListValue("partitions");
String[] types = (String[])Array.newInstance(String.class, _partProperties.size());
Arrays.fill(types, _type.getName());
partitions.setClassNames(types);
partitions.setProperties(_partProperties.toArray(new String[_partProperties.size()]));
DataCache[] array = (DataCache[])partitions.instantiate(_type, conf);
for (DataCache part : array) {
if (part.getName() == null)
throw new UserException(_loc.get("partition-cache-null-partition", parts));
if (_partitions.containsKey(part.getName()))
throw new UserException(_loc.get("partition-cache-duplicate-partition", part.getName(), parts));
if (part.getName().equals(DataCache.NAME_DEFAULT))
throw new UserException(_loc.get("partition-cache-default-partition", part.getName(), parts));
_partitions.put(part.getName(), part);
}
}
/**
* Returns the individual partition configuration properties.
*/
public List<String> getPartitions() {
return _partProperties;
}
@Override
public DataCache getPartition(String name, boolean create) {
return _partitions.get(name);
}
/**
* Gets the name of the configured partitions.
*/
@Override
public Set<String> getPartitionNames() {
return _partitions.keySet();
}
/**
* Always returns true.
*/
@Override
public final boolean isPartitioned() {
return !_partitions.isEmpty();
}
@Override
public void endConfiguration() {
if (!isPartitioned())
conf.getConfigurationLog().warn(_loc.get("partition-cache-no-config"));
}
/**
* Parses property string of the form <code>(p1),(p2),(p3)</code> to produce a list of
* <code>p1</code>, <code>p2</code> and <code>p3</code>. The component strings
* <code>p1</code> etc. must be enclosed in parentheses and separated by comma.
* plug-in string to produce a list of
*
* @param properties property string of the form <code>(p1),(p2),(p3)</code>
*/
private void parsePartitionProperties(String full) {
String properties = new String(full);
while (true) {
if (properties == null)
break;
properties = properties.trim();
if (properties.length() == 0)
break;
if (properties.startsWith(",")) {
properties = properties.substring(1);
} else if (!_partProperties.isEmpty()) {
throw new UserException(_loc.get("partition-cache-parse-error-comma", full, properties));
}
if (properties.startsWith("(") && properties.endsWith(")")) {
int i = properties.indexOf(")");
String p = properties.substring(1,i); // exclude the end parentheses
_partProperties.add(p);
if (i < properties.length()-1) {
properties = properties.substring(i+1);
} else {
break;
}
} else {
throw new UserException(_loc.get("partition-cache-parse-error-paren", full, properties));
}
}
}
}