/*****************************************************************
* 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.cayenne.crypto.transformer.value;
import org.apache.cayenne.crypto.key.KeySource;
import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.Key;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A {@link ValueTransformerFactory} that creates encryptors/decryptors that are
* taking advantage of the JCE (Java Cryptography Extension) ciphers.
*
* @since 4.0
*/
public class DefaultValueTransformerFactory implements ValueTransformerFactory {
private static final Logger logger = LoggerFactory.getLogger(DefaultValueTransformerFactory.class);
public static final String DB_TO_BYTE_CONVERTERS_KEY =
"org.apache.cayenne.crypto.transformer.value.DefaultValueTransformerFactory.dbToBytes";
public static final String OBJECT_TO_BYTE_CONVERTERS_KEY =
"org.apache.cayenne.crypto.transformer.value.DefaultValueTransformerFactory.objectToBytes";
private final Key defaultKey;
private final Map<String, BytesConverter<?>> objectToBytes;
private final Map<Integer, BytesConverter<?>> dbToBytes;
private final ConcurrentMap<DbAttribute, ValueEncryptor> encryptors;
private final ConcurrentMap<DbAttribute, ValueDecryptor> decryptors;
public DefaultValueTransformerFactory(@Inject KeySource keySource,
@Inject(DB_TO_BYTE_CONVERTERS_KEY) Map<String, BytesConverter<?>> dbToBytes,
@Inject(OBJECT_TO_BYTE_CONVERTERS_KEY) Map<String, BytesConverter<?>> objectToBytes) {
this.defaultKey = keySource.getKey(keySource.getDefaultKeyAlias());
this.encryptors = new ConcurrentHashMap<>();
this.decryptors = new ConcurrentHashMap<>();
this.objectToBytes = objectToBytes;
Map<Integer, BytesConverter<?>> m = new HashMap<>();
for (Map.Entry<String, BytesConverter<?>> extraConverter : dbToBytes.entrySet()) {
m.put(Integer.valueOf(extraConverter.getKey()), extraConverter.getValue());
}
this.dbToBytes = m;
}
@Override
public ValueDecryptor decryptor(DbAttribute a) {
ValueDecryptor e = decryptors.get(a);
if (e == null) {
ValueDecryptor newTransformer = createDecryptor(a);
ValueDecryptor oldTransformer = decryptors.putIfAbsent(a, newTransformer);
e = oldTransformer != null ? oldTransformer : newTransformer;
}
return e;
}
@Override
public ValueEncryptor encryptor(DbAttribute a) {
ValueEncryptor e = encryptors.get(a);
if (e == null) {
ValueEncryptor newTransformer = createEncryptor(a);
ValueEncryptor oldTransformer = encryptors.putIfAbsent(a, newTransformer);
e = oldTransformer != null ? oldTransformer : newTransformer;
}
return e;
}
protected ValueEncryptor createEncryptor(DbAttribute a) {
String type = getJavaType(a);
BytesConverter<?> toBytes = objectToBytes.get(type);
if (toBytes == null) {
throw new IllegalArgumentException("The type " + type + " for attribute " + a
+ " has no object-to-bytes conversion");
}
BytesConverter<?> fromBytes = dbToBytes.get(a.getType());
if (fromBytes == null) {
throw new IllegalArgumentException("The type " + TypesMapping.getSqlNameByType(a.getType())
+ " for attribute " + a + " has no bytes-to-db conversion");
}
return new DefaultValueEncryptor(toBytes, fromBytes);
}
protected ValueDecryptor createDecryptor(DbAttribute a) {
BytesConverter<?> toBytes = dbToBytes.get(a.getType());
if (toBytes == null) {
throw new IllegalArgumentException("The type " + TypesMapping.getSqlNameByType(a.getType())
+ " for attribute " + a + " has no db-to-bytes conversion");
}
String type = getJavaType(a);
BytesConverter<?> fromBytes = objectToBytes.get(type);
if (fromBytes == null) {
throw new IllegalArgumentException("The type " + type + " for attribute " + a
+ " has no bytes-to-object conversion");
}
return new DefaultValueDecryptor(toBytes, fromBytes, defaultKey);
}
// TODO: calculating Java type of ObjAttribute may become unneeded per
// CAY-1752, as DbAttribute will have it.
protected String getJavaType(DbAttribute a) {
DbEntity dbEntity = a.getEntity();
DataMap dataMap = dbEntity.getDataMap();
Collection<String> javaTypes = new HashSet<>();
for(ObjEntity objEntity : dataMap.getMappedEntities(dbEntity)) {
for (ObjAttribute oa : objEntity.getAttributes()) {
// TODO: this won't pick up flattened attributes
if (a.getName().equals(oa.getDbAttributePath())) {
javaTypes.add(oa.getType());
}
}
}
if (javaTypes.size() != 1) {
String javaType = TypesMapping.getJavaBySqlType(a.getType());
String attributeName = dbEntity.getName() + "." + a.getName();
String msg = javaTypes.size() > 1 ? "ObjAttributes with different java types" : "No ObjAttributes";
// Warn user about this problem as there is nothing else we can do
logger.warn(msg + " bound to DbAttribute '" + attributeName + "', " + javaType + " type will be used.");
return javaType;
}
return javaTypes.iterator().next();
}
}