adutra commented on code in PR #493:
URL: https://github.com/apache/polaris/pull/493#discussion_r1869219908
##########
polaris-service/src/main/java/org/apache/polaris/service/auth/DefaultPolarisAuthenticator.java:
##########
@@ -36,16 +36,7 @@ public Optional<AuthenticatedPolarisPrincipal>
authenticate(String credentials)
return getPrincipal(decodedToken);
}
- @Override
- public void setMetaStoreManagerFactory(MetaStoreManagerFactory
metaStoreManagerFactory) {
- super.setMetaStoreManagerFactory(metaStoreManagerFactory);
- if (tokenBrokerFactory instanceof HasMetaStoreManagerFactory) {
- ((HasMetaStoreManagerFactory) tokenBrokerFactory)
- .setMetaStoreManagerFactory(metaStoreManagerFactory);
- }
- }
-
- @JsonProperty("tokenBroker")
+ @Inject
Review Comment:
Please turn into constructor injection.
##########
polaris-service/src/main/java/org/apache/polaris/service/context/RealmScopeContext.java:
##########
@@ -0,0 +1,82 @@
+/*
+ * 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.polaris.service.context;
+
+import jakarta.inject.Singleton;
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.core.context.RealmScope;
+import org.glassfish.hk2.api.ActiveDescriptor;
+import org.glassfish.hk2.api.Context;
+import org.glassfish.hk2.api.ServiceHandle;
+
+@Singleton
+public class RealmScopeContext implements Context<RealmScope> {
+ private final Map<String, Map<ActiveDescriptor<?>, Object>> contexts = new
ConcurrentHashMap<>();
+
+ @Override
+ public Class<? extends Annotation> getScope() {
+ return RealmScope.class;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <U> U findOrCreate(ActiveDescriptor<U> activeDescriptor,
ServiceHandle<?> root) {
+ RealmContext realmContext =
CallContext.getCurrentContext().getRealmContext();
+ Map<ActiveDescriptor<?>, Object> contextMap =
+ contexts.computeIfAbsent(realmContext.getRealmIdentifier(), k -> new
ConcurrentHashMap<>());
+ return (U) contextMap.computeIfAbsent(activeDescriptor, k ->
activeDescriptor.create(root));
+ }
+
+ @Override
+ public boolean containsKey(ActiveDescriptor<?> descriptor) {
+ RealmContext realmContext =
CallContext.getCurrentContext().getRealmContext();
+ Map<ActiveDescriptor<?>, Object> contextMap =
+ contexts.computeIfAbsent(realmContext.getRealmIdentifier(), k -> new
HashMap<>());
+ return contextMap.containsKey(descriptor);
+ }
+
+ @Override
+ public void destroyOne(ActiveDescriptor<?> descriptor) {
+ RealmContext realmContext =
CallContext.getCurrentContext().getRealmContext();
+ Map<ActiveDescriptor<?>, Object> contextMap =
+ contexts.computeIfAbsent(realmContext.getRealmIdentifier(), k -> new
HashMap<>());
+ contextMap.remove(descriptor);
+ }
+
+ @Override
+ public boolean supportsNullCreation() {
+ return false;
+ }
+
+ @Override
+ public boolean isActive() {
+ return CallContext.getCurrentContext() != null
Review Comment:
Here we are using `CallContext.getCurrentContext()`, which internally relies
on thread-locals. This is NOT going to work well with CDI. We have already
opened an issue to raise awareness of that: #463. My long-term vision for
`CallContext` and alike is:
* `CallContext` and `PolarisCallContext` must disappear eventually;
* `RealmContext` can stay, and become request-scoped.
In the meanwhile, since removing `CallContext` is a large change, what I did
in the Quarkus PR is that I turned all 3 beans into request-scoped, and they
are `@Inject`-ed whenever required, avoiding `CallContext.getCurrentContext()`
as much as possible.
##########
polaris-service/src/main/java/org/apache/polaris/service/auth/JWTRSAKeyPairFactory.java:
##########
@@ -39,7 +39,7 @@ public TokenBroker apply(RealmContext realmContext) {
maxTokenGenerationInSeconds);
}
- @Override
+ @Inject
Review Comment:
setter -> constructor
##########
polaris-service/src/main/java/org/apache/polaris/service/auth/JWTSymmetricKeyFactory.java:
##########
@@ -68,7 +68,7 @@ public void setSecret(String secret) {
this.secret = secret;
}
- @Override
+ @Inject
public void setMetaStoreManagerFactory(MetaStoreManagerFactory
metaStoreManagerFactory) {
Review Comment:
setter -> constructor
##########
polaris-service/src/main/java/org/apache/polaris/service/auth/TestOAuth2ApiService.java:
##########
@@ -108,11 +108,8 @@ private String getPrincipalName(String clientId) {
}
}
- @Override
+ @Inject
public void setMetaStoreManagerFactory(MetaStoreManagerFactory
metaStoreManagerFactory) {
Review Comment:
setter -> constructor
##########
polaris-core/src/main/java/org/apache/polaris/core/persistence/LocalPolarisMetaStoreManagerFactory.java:
##########
@@ -54,7 +54,7 @@ public abstract class
LocalPolarisMetaStoreManagerFactory<StoreType>
final Map<String, Supplier<PolarisMetaStoreSession>> sessionSupplierMap =
new HashMap<>();
protected final PolarisDiagnostics diagServices = new
PolarisDefaultDiagServiceImpl();
- protected PolarisStorageIntegrationProvider storageIntegration;
+ @Inject protected PolarisStorageIntegrationProvider storageIntegration;
Review Comment:
This should be removed from here (along with the setter below) because this
field is not used in this class.
Instead it should be injected on concrete subtypes that actually need it.
##########
polaris-service/src/main/java/org/apache/polaris/service/context/RealmScopeContext.java:
##########
@@ -0,0 +1,82 @@
+/*
+ * 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.polaris.service.context;
+
+import jakarta.inject.Singleton;
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.core.context.RealmScope;
+import org.glassfish.hk2.api.ActiveDescriptor;
+import org.glassfish.hk2.api.Context;
+import org.glassfish.hk2.api.ServiceHandle;
+
+@Singleton
+public class RealmScopeContext implements Context<RealmScope> {
+ private final Map<String, Map<ActiveDescriptor<?>, Object>> contexts = new
ConcurrentHashMap<>();
+
+ @Override
+ public Class<? extends Annotation> getScope() {
+ return RealmScope.class;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <U> U findOrCreate(ActiveDescriptor<U> activeDescriptor,
ServiceHandle<?> root) {
Review Comment:
I think we are mixing concepts here, a request scoped bean should not
outlive the request. In the Quarkus PR the `RealmContext` is a request-scoped
bean, and one instance of it is created for each request. It two requests
target the same realm, they will get two distinct instances of `RealmContext`,
but each would have the same realm ID.
##########
polaris-service/src/main/java/org/apache/polaris/service/auth/DefaultOAuth2ApiService.java:
##########
@@ -39,8 +40,8 @@
* Default implementation of the {@link OAuth2ApiService} that generates a JWT
token for the client
* if the client secret matches.
*/
-@JsonTypeName("default")
-public class DefaultOAuth2ApiService implements OAuth2ApiService,
HasMetaStoreManagerFactory {
+@Named("default")
Review Comment:
I agree that `@Named` has some shortcomings. From [CDI spec
3.0](https://jakarta.ee/specifications/cdi/3.0/jakarta-cdi-spec-3.0#named_at_injection_point):
> The use of `@Named` as an injection point qualifier is not recommended,
except in the case of integration with legacy code that uses string-based names
to identify beans.
One of them is that the name is application-wide, so you cannot have two
beans named `@Named("default")`.
Instead, we could use some other qualifier. People generally use
`@Identifier` from Smallrye-Common library. The Quarkus PR is using that for
now.
This article explains the pros and cons of each annotation:
https://dev.to/yanev/unveiling-challenges-with-named-67p
##########
polaris-service/src/main/java/org/apache/polaris/service/auth/DefaultOAuth2ApiService.java:
##########
@@ -121,14 +122,14 @@ public Response getToken(
.build();
}
- @Override
+ @Inject
Review Comment:
Can we turn these setters into constructor injection? Also the
`HasMetaStoreManagerFactory` and other similar ones should be removed, they are
not needed in a CDI world.
##########
polaris-service/src/main/java/org/apache/polaris/service/persistence/cache/EntityCacheFactory.java:
##########
@@ -0,0 +1,47 @@
+/*
+ * 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.polaris.service.persistence.cache;
+
+import jakarta.inject.Inject;
+import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.context.RealmScope;
+import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
+import org.apache.polaris.core.persistence.cache.EntityCache;
+import org.glassfish.hk2.api.Factory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EntityCacheFactory implements Factory<EntityCache> {
+ private static Logger LOGGER =
LoggerFactory.getLogger(EntityCacheFactory.class);
+ @Inject PolarisMetaStoreManager metaStoreManager;
+
+ @RealmScope
+ @Override
+ public EntityCache provide() {
+ LOGGER.debug(
+ "Creating new EntityCache instance for realm {}",
+
CallContext.getCurrentContext().getRealmContext().getRealmIdentifier());
Review Comment:
`RealmContext` should be injected, not retrieved from thread locals. That's
what `RealmEntityManagerFactory` does, cf.
`getOrCreateEntityManager(RealmContext)`.
##########
polaris-service/src/main/java/org/apache/polaris/service/context/RealmScopeContext.java:
##########
@@ -0,0 +1,82 @@
+/*
+ * 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.polaris.service.context;
+
+import jakarta.inject.Singleton;
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.core.context.RealmScope;
+import org.glassfish.hk2.api.ActiveDescriptor;
+import org.glassfish.hk2.api.Context;
+import org.glassfish.hk2.api.ServiceHandle;
+
+@Singleton
+public class RealmScopeContext implements Context<RealmScope> {
Review Comment:
So, I'm really, really not comfortable with this `RealmScopeContext`.
Speaking from experience, most custom scopes are hard to maintain, and can
introduce subtle bugs that are hard to chase. For instance, it's never easy to
know when to close a custom scope.
OTOH, most custom scoped-beans can actually be retrofitted to be
request-scoped beans with a bit of refactoring. This is imho much safer because
we don't have to care about when to open or when to close the scope.
In the Quarkus PR, `RealmContext` is a request-scoped bean, and that's
working fine.
##########
polaris-service/src/main/java/org/apache/polaris/service/config/RealmEntityManagerFactory.java:
##########
@@ -18,29 +18,39 @@
*/
package org.apache.polaris.service.config;
+import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.core.context.RealmScope;
import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
import org.apache.polaris.core.persistence.PolarisEntityManager;
+import org.apache.polaris.core.persistence.cache.EntityCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Gets or creates PolarisEntityManager instances based on config values and
RealmContext. */
+@RealmScope
public class RealmEntityManagerFactory {
Review Comment:
I disagree here, this is clearly an application-scoped bean, holding
long-lived instances of `PolarisEntityManager` keyed by realm id.
##########
polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisStorageIntegrationProviderImpl.java:
##########
@@ -16,29 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.service.storage;
+package org.apache.polaris.core.storage;
Review Comment:
Why move an implementation class to core?
##########
polaris-service/src/main/java/org/apache/polaris/service/persistence/InMemoryPolarisMetaStoreManagerFactory.java:
##########
@@ -35,9 +35,10 @@
import org.apache.polaris.core.persistence.PolarisTreeMapMetaStoreSessionImpl;
import org.apache.polaris.core.persistence.PolarisTreeMapStore;
-@JsonTypeName("in-memory")
+@Named("in-memory")
+@Singleton
Review Comment:
We have to decide between `@Singleton` and `@ApplicationScoped` – unless we
have a good reason to use the former, the latter is preferred.
##########
polaris-service/src/main/java/org/apache/polaris/service/BootstrapRealmsCommand.java:
##########
@@ -48,17 +46,11 @@ protected void run(
Bootstrap<PolarisApplicationConfig> bootstrap,
Namespace namespace,
PolarisApplicationConfig configuration) {
- MetaStoreManagerFactory metaStoreManagerFactory =
configuration.getMetaStoreManagerFactory();
+ MetaStoreManagerFactory metaStoreManagerFactory =
Review Comment:
Commands (boostrap/purge) are going to be fun to adapt to CDI. I suggest we
look into them later but my feeling is that we would need to move them to
separate modules.
##########
polaris-service/src/main/java/org/apache/polaris/service/persistence/cache/EntityCacheFactory.java:
##########
@@ -0,0 +1,47 @@
+/*
+ * 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.polaris.service.persistence.cache;
+
+import jakarta.inject.Inject;
+import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.context.RealmScope;
+import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
+import org.apache.polaris.core.persistence.cache.EntityCache;
+import org.glassfish.hk2.api.Factory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EntityCacheFactory implements Factory<EntityCache> {
+ private static Logger LOGGER =
LoggerFactory.getLogger(EntityCacheFactory.class);
+ @Inject PolarisMetaStoreManager metaStoreManager;
+
+ @RealmScope
Review Comment:
Again I think it's much easier to turn this bean into application-scoped and
have it hold a map of entity caches keyed by realm id, like
`RealmEntityManagerFactory`.
##########
server-templates/api.mustache:
##########
@@ -83,6 +85,7 @@ public class {{classname}} {
private final {{classname}}Service service;
+ @Inject
Review Comment:
If you turn `RealmContext` into request-scoped, a nice benefit that comes
for free is that you could inject it directly into the REST resources generated
from the open-api spec. This way, the realm is immediately available and
injected everywhere. E.g. the /config endpoint could become:
```java
public Response getConfig(
@QueryParam("warehouse") String warehouse,
@Context RealmContext realmContext,
@Context SecurityContext securityContext) {
return service.getConfig(warehouse, realmContext, securityContext);
}
```
##########
polaris-service/src/main/java/org/apache/polaris/service/context/DefaultContextResolver.java:
##########
@@ -166,7 +165,7 @@ private static Map<String, String>
parseBearerTokenAsKvPairs(Map<String, String>
return parsedProperties;
}
- @Override
+ @Inject
Review Comment:
setter -> constructor
##########
polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisEntityManager.java:
##########
@@ -51,11 +51,14 @@ public class PolarisEntityManager {
/**
* @param metaStoreManager the metastore manager for the current realm
* @param credentialCache the storage credential cache for the current realm
+ * @param entityCache the entity cache
*/
public PolarisEntityManager(
- PolarisMetaStoreManager metaStoreManager, StorageCredentialCache
credentialCache) {
+ PolarisMetaStoreManager metaStoreManager,
Review Comment:
I asked myself this question as well when doing the Quarkus port. But in the
end I left `PolarisEntityManager` as a non-CDI class. There might be room for
improvement around here.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]