keycloak-aplcache
Changes
audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java 34(+34 -0)
audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory 1(+1 -0)
audit/jpa/pom.xml 26(+26 -0)
Details
diff --git a/audit/api/src/main/java/org/keycloak/audit/Audit.java b/audit/api/src/main/java/org/keycloak/audit/Audit.java
index ad23701..682ceb4 100644
--- a/audit/api/src/main/java/org/keycloak/audit/Audit.java
+++ b/audit/api/src/main/java/org/keycloak/audit/Audit.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderFactoryLoader;
import java.util.HashMap;
import java.util.LinkedList;
@@ -20,14 +21,17 @@ public class Audit {
private Event event;
public static Audit create(RealmModel realm, String ipAddress) {
+ ProviderFactoryLoader<AuditListenerFactory> loader = ProviderFactoryLoader.load(AuditListenerFactory.class);
+
List<AuditListener> listeners = null;
if (realm.getAuditListeners() != null) {
listeners = new LinkedList<AuditListener>();
for (String id : realm.getAuditListeners()) {
- listeners.add(AuditLoader.load(id));
+ listeners.add(loader.find(id).create());
}
}
+
return new Audit(listeners, new Event()).realm(realm).ipAddress(ipAddress);
}
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditListener.java b/audit/api/src/main/java/org/keycloak/audit/AuditListener.java
index e521300..1425379 100644
--- a/audit/api/src/main/java/org/keycloak/audit/AuditListener.java
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditListener.java
@@ -1,11 +1,11 @@
package org.keycloak.audit;
+import org.keycloak.provider.Provider;
+
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public interface AuditListener {
-
- public String getId();
+public interface AuditListener extends Provider {
public void onEvent(Event event);
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditListenerFactory.java b/audit/api/src/main/java/org/keycloak/audit/AuditListenerFactory.java
new file mode 100644
index 0000000..6ffeb20
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditListenerFactory.java
@@ -0,0 +1,10 @@
+package org.keycloak.audit;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface AuditListenerFactory extends ProviderFactory<AuditListener> {
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditProviderFactory.java b/audit/api/src/main/java/org/keycloak/audit/AuditProviderFactory.java
new file mode 100644
index 0000000..8d6331e
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditProviderFactory.java
@@ -0,0 +1,10 @@
+package org.keycloak.audit;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface AuditProviderFactory extends ProviderFactory<AuditProvider> {
+
+}
diff --git a/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java
index bec3b9d..d5381e2 100644
--- a/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java
+++ b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java
@@ -11,11 +11,10 @@ import java.util.Map;
*/
public class JBossLoggingAuditListener implements AuditListener {
- private static final Logger logger = Logger.getLogger("org.keycloak.audit");
+ private final Logger logger;
- @Override
- public String getId() {
- return "jboss-logging";
+ public JBossLoggingAuditListener(Logger logger) {
+ this.logger = logger;
}
@Override
@@ -60,4 +59,8 @@ public class JBossLoggingAuditListener implements AuditListener {
}
}
+ @Override
+ public void close() {
+ }
+
}
diff --git a/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java
new file mode 100644
index 0000000..420bd67
--- /dev/null
+++ b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java
@@ -0,0 +1,34 @@
+package org.keycloak.audit.log;
+
+import org.jboss.logging.Logger;
+import org.keycloak.audit.AuditListener;
+import org.keycloak.audit.AuditListenerFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JBossLoggingAuditListenerFactory implements AuditListenerFactory {
+
+ public static final String ID = "jboss-logging";
+
+ private static final Logger logger = Logger.getLogger("org.keycloak.audit");
+
+ @Override
+ public AuditListener create() {
+ return new JBossLoggingAuditListener(logger);
+ }
+
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+}
diff --git a/audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory b/audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory
new file mode 100644
index 0000000..2370355
--- /dev/null
+++ b/audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory
@@ -0,0 +1 @@
+org.keycloak.audit.log.JBossLoggingAuditListenerFactory
\ No newline at end of file
audit/jpa/pom.xml 26(+26 -0)
diff --git a/audit/jpa/pom.xml b/audit/jpa/pom.xml
index 747bcc0..9dd97b9 100755
--- a/audit/jpa/pom.xml
+++ b/audit/jpa/pom.xml
@@ -20,10 +20,36 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-audit-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate.javax.persistence</groupId>
+ <artifactId>hibernate-jpa-2.0-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-entitymanager</artifactId>
+ <version>${hibernate.entitymanager.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ </dependency>
</dependencies>
</project>
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/EventEntity.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/EventEntity.java
new file mode 100644
index 0000000..a8fd531
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/EventEntity.java
@@ -0,0 +1,103 @@
+package org.keycloak.audit.jpa;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Entity
+public class EventEntity {
+
+ @Id
+ private String id;
+
+ private long time;
+
+ private String event;
+
+ private String realmId;
+
+ private String clientId;
+
+ private String userId;
+
+ private String ipAddress;
+
+ private String error;
+
+ private String detailsJson;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public long getTime() {
+ return time;
+ }
+
+ public void setTime(long time) {
+ this.time = time;
+ }
+
+ public String getEvent() {
+ return event;
+ }
+
+ public void setEvent(String event) {
+ this.event = event;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+ public String getDetailsJson() {
+ return detailsJson;
+ }
+
+ public void setDetailsJson(String detailsJson) {
+ this.detailsJson = detailsJson;
+ }
+
+}
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProvider.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProvider.java
new file mode 100644
index 0000000..9d10da7
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProvider.java
@@ -0,0 +1,75 @@
+package org.keycloak.audit.jpa;
+
+import org.json.JSONObject;
+import org.keycloak.audit.AuditProvider;
+import org.keycloak.audit.Event;
+import org.keycloak.audit.EventQuery;
+
+import javax.persistence.EntityManager;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaAuditProvider implements AuditProvider {
+
+ private EntityManager em;
+
+ public JpaAuditProvider(EntityManager em) {
+ this.em = em;
+ }
+
+ @Override
+ public EventQuery createQuery() {
+ return new JpaEventQuery(em);
+ }
+
+ @Override
+ public void onEvent(Event event) {
+ em.getTransaction().begin();
+ em.persist(convert(event));
+ em.getTransaction().commit();
+ }
+
+ @Override
+ public void close() {
+ em.close();
+ }
+
+ static EventEntity convert(Event o) {
+ EventEntity e = new EventEntity();
+ e.setId(UUID.randomUUID().toString());
+ e.setTime(o.getTime());
+ e.setEvent(o.getEvent());
+ e.setRealmId(o.getRealmId());
+ e.setClientId(o.getClientId());
+ e.setUserId(o.getUserId());
+ e.setIpAddress(o.getIpAddress());
+ e.setError(o.getError());
+ e.setDetailsJson(new JSONObject(o.getDetails()).toString());
+ return e;
+ }
+
+ static Event convert(EventEntity o) {
+ Event e = new Event();
+ e.setTime(o.getTime());
+ e.setEvent(o.getEvent());
+ e.setRealmId(o.getRealmId());
+ e.setClientId(o.getClientId());
+ e.setUserId(o.getUserId());
+ e.setIpAddress(o.getIpAddress());
+ e.setError(o.getError());
+
+ JSONObject object = new JSONObject(o.getDetailsJson());
+ Map<String, String> details = new HashMap<String, String>();
+ for (Object k : object.keySet()) {
+ details.put((String) k, object.getString((String) k));
+ }
+
+ e.setDetails(details);
+ return e;
+ }
+
+}
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProviderFactory.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProviderFactory.java
new file mode 100644
index 0000000..e05ab63
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProviderFactory.java
@@ -0,0 +1,37 @@
+package org.keycloak.audit.jpa;
+
+import org.keycloak.audit.AuditProvider;
+import org.keycloak.audit.AuditProviderFactory;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaAuditProviderFactory implements AuditProviderFactory {
+
+ public static final String ID = "jpa";
+ private EntityManagerFactory emf;
+
+ @Override
+ public AuditProvider create() {
+ return new JpaAuditProvider(emf.createEntityManager());
+ }
+
+ @Override
+ public void init() {
+ emf = Persistence.createEntityManagerFactory("jpa-keycloak-audit-store");
+ }
+
+ @Override
+ public void close() {
+ emf.close();
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+}
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaEventQuery.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaEventQuery.java
new file mode 100644
index 0000000..f4ec884
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaEventQuery.java
@@ -0,0 +1,100 @@
+package org.keycloak.audit.jpa;
+
+import org.keycloak.audit.Event;
+import org.keycloak.audit.EventQuery;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaEventQuery implements EventQuery {
+
+ private final EntityManager em;
+ private final CriteriaBuilder cb;
+ private final CriteriaQuery<EventEntity> cq;
+ private final Root<EventEntity> root;
+ private final ArrayList<Predicate> predicates;
+ private Integer firstResult;
+ private Integer maxResults;
+
+ public JpaEventQuery(EntityManager em) {
+ this.em = em;
+
+ cb = em.getCriteriaBuilder();
+ cq = cb.createQuery(EventEntity.class);
+ root = cq.from(EventEntity.class);
+ predicates = new ArrayList<Predicate>(4);
+ }
+
+ @Override
+ public EventQuery event(String event) {
+ predicates.add(cb.equal(root.get("event"), event));
+ return this;
+ }
+
+ @Override
+ public EventQuery realm(String realmId) {
+ predicates.add(cb.equal(root.get("realmId"), realmId));
+ return this;
+ }
+
+ @Override
+ public EventQuery client(String clientId) {
+ predicates.add(cb.equal(root.get("clientId"), clientId));
+ return this;
+ }
+
+ @Override
+ public EventQuery user(String userId) {
+ predicates.add(cb.equal(root.get("userId"), userId));
+ return this;
+ }
+
+ @Override
+ public EventQuery firstResult(int firstResult) {
+ this.firstResult = firstResult;
+ return this;
+ }
+
+ @Override
+ public EventQuery maxResults(int maxResults) {
+ this.maxResults = maxResults;
+ return this;
+ }
+
+ @Override
+ public List<Event> getResultList() {
+ if (!predicates.isEmpty()) {
+ cq.where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
+ }
+
+ cq.orderBy(cb.asc(root.get("time")), cb.asc(root.get("id")));
+
+ TypedQuery<EventEntity> query = em.createQuery(cq);
+
+ if (firstResult != null) {
+ query.setFirstResult(firstResult);
+ }
+
+ if (maxResults != null) {
+ query.setMaxResults(maxResults);
+ }
+
+ List<Event> events = new LinkedList<Event>();
+ for (EventEntity e : query.getResultList()) {
+ events.add(JpaAuditProvider.convert(e));
+ }
+
+ return events;
+ }
+
+}
diff --git a/audit/jpa/src/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory b/audit/jpa/src/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory
new file mode 100644
index 0000000..e04a028
--- /dev/null
+++ b/audit/jpa/src/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory
@@ -0,0 +1 @@
+org.keycloak.audit.jpa.JpaAuditProviderFactory
\ No newline at end of file
diff --git a/audit/jpa/src/src/test/java/org/keycloak/audit/jpa/JpaAuditProviderTest.java b/audit/jpa/src/src/test/java/org/keycloak/audit/jpa/JpaAuditProviderTest.java
new file mode 100644
index 0000000..ed86905
--- /dev/null
+++ b/audit/jpa/src/src/test/java/org/keycloak/audit/jpa/JpaAuditProviderTest.java
@@ -0,0 +1,80 @@
+package org.keycloak.audit.jpa;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.audit.AuditProvider;
+import org.keycloak.audit.AuditProviderFactory;
+import org.keycloak.audit.Event;
+import org.keycloak.provider.ProviderFactoryLoader;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaAuditProviderTest {
+
+ private AuditProviderFactory factory;
+ private AuditProvider provider;
+
+ @Before
+ public void before() {
+ ProviderFactoryLoader<AuditProviderFactory> loader = ProviderFactoryLoader.load(AuditProviderFactory.class);
+ factory = loader.find(JpaAuditProviderFactory.ID);
+ factory.init();
+
+ provider = factory.create();
+ }
+
+ @After
+ public void after() {
+ factory.close();
+ }
+
+ @Test
+ public void save() {
+ provider.onEvent(create("event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+ }
+
+ @Test
+ public void query() {
+ provider.onEvent(create("event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+ provider.onEvent(create("event2", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+ provider.onEvent(create("event", "realmId2", "clientId", "userId", "127.0.0.1", "error"));
+ provider.onEvent(create("event", "realmId", "clientId2", "userId", "127.0.0.1", "error"));
+ provider.onEvent(create("event", "realmId", "clientId", "userId2", "127.0.0.1", "error"));
+
+ Assert.assertEquals(4, provider.createQuery().client("clientId").getResultList().size());
+ Assert.assertEquals(4, provider.createQuery().realm("realmId").getResultList().size());
+ Assert.assertEquals(4, provider.createQuery().event("event").getResultList().size());
+ Assert.assertEquals(4, provider.createQuery().user("userId").getResultList().size());
+
+ Assert.assertEquals(1, provider.createQuery().user("userId").event("event2").getResultList().size());
+
+ Assert.assertEquals(2, provider.createQuery().maxResults(2).getResultList().size());
+ Assert.assertEquals(1, provider.createQuery().firstResult(4).getResultList().size());
+ }
+
+ private Event create(String event, String realmId, String clientId, String userId, String ipAddress, String error) {
+ Event e = new Event();
+ e.setTime(System.currentTimeMillis());
+ e.setEvent(event);
+ e.setRealmId(realmId);
+ e.setClientId(clientId);
+ e.setUserId(userId);
+ e.setIpAddress(ipAddress);
+ e.setError(error);
+
+ Map<String, String> details = new HashMap<String, String>();
+ details.put("key1", "value1");
+ details.put("key2", "value2");
+
+ e.setDetails(details);
+
+ return e;
+ }
+
+}
diff --git a/audit/jpa/src/src/test/resources/META-INF/persistence.xml b/audit/jpa/src/src/test/resources/META-INF/persistence.xml
new file mode 100755
index 0000000..f537808
--- /dev/null
+++ b/audit/jpa/src/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,22 @@
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+ version="1.0">
+ <persistence-unit name="jpa-keycloak-audit-store" transaction-type="RESOURCE_LOCAL">
+ <provider>org.hibernate.ejb.HibernatePersistence</provider>
+
+ <class>org.keycloak.audit.jpa.EventEntity</class>
+
+ <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+ <properties>
+ <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
+ <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
+ <property name="hibernate.connection.username" value="sa"/>
+ <property name="hibernate.connection.password" value=""/>
+ <property name="hibernate.hbm2ddl.auto" value="create-drop" />
+ <property name="hibernate.show_sql" value="false" />
+ <property name="hibernate.format_sql" value="true" />
+ </properties>
+ </persistence-unit>
+</persistence>
diff --git a/core/src/main/java/org/keycloak/provider/Provider.java b/core/src/main/java/org/keycloak/provider/Provider.java
new file mode 100644
index 0000000..3401266
--- /dev/null
+++ b/core/src/main/java/org/keycloak/provider/Provider.java
@@ -0,0 +1,10 @@
+package org.keycloak.provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Provider {
+
+ public void close();
+
+}
diff --git a/core/src/main/java/org/keycloak/provider/ProviderFactory.java b/core/src/main/java/org/keycloak/provider/ProviderFactory.java
new file mode 100644
index 0000000..720538d
--- /dev/null
+++ b/core/src/main/java/org/keycloak/provider/ProviderFactory.java
@@ -0,0 +1,16 @@
+package org.keycloak.provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ProviderFactory<T extends Provider> {
+
+ public T create();
+
+ public void init();
+
+ public void close();
+
+ public String getId();
+
+}
diff --git a/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java b/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java
new file mode 100644
index 0000000..51b00dc
--- /dev/null
+++ b/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java
@@ -0,0 +1,88 @@
+package org.keycloak.provider;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProviderFactoryLoader<P extends ProviderFactory> implements Iterable<P> {
+
+ private ServiceLoader<P> serviceLoader;
+
+ private ProviderFactoryLoader(ServiceLoader<P> serviceLoader) {
+ this.serviceLoader = serviceLoader;
+ }
+
+ public static <P extends ProviderFactory> ProviderFactoryLoader<P> load(Class<P> service) {
+ return new ProviderFactoryLoader(ServiceLoader.load(service));
+ }
+
+ public static <P extends ProviderFactory> ProviderFactoryLoader<P> load(Class<P> service, ClassLoader loader) {
+ return new ProviderFactoryLoader(ServiceLoader.load(service, loader));
+ }
+
+ public P find(String id) {
+ Iterator<P> itr = iterator();
+ while (itr.hasNext()) {
+ P p = itr.next();
+ if (p.getId() != null && p.getId().equals(id)) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Iterator<P> iterator() {
+ return new ProviderFactoryIterator(serviceLoader.iterator());
+ }
+
+ public void close() {
+
+ }
+
+ private static class ProviderFactoryIterator<P> implements Iterator<P> {
+
+ private Iterator<P> itr;
+
+ private P next;
+
+ private ProviderFactoryIterator(Iterator<P> itr) {
+ this.itr = itr;
+ setNext();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ @Override
+ public P next() {
+ P n = next;
+ setNext();
+ return n;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ private void setNext() {
+ next = null;
+ while (itr.hasNext()) {
+ if (itr.hasNext()) {
+ P n = itr.next();
+ if (!System.getProperties().containsKey(n.getClass().getName() + ".disabled")) {
+ next = n;
+ return;
+ }
+ }
+ }
+ }
+
+ }
+
+}