diff options
Diffstat (limited to 'src/com/p4square/grow')
| -rw-r--r-- | src/com/p4square/grow/ccb/CCBProgressReporter.java | 104 | ||||
| -rw-r--r-- | src/com/p4square/grow/ccb/CCBUser.java | 37 | ||||
| -rw-r--r-- | src/com/p4square/grow/ccb/CCBUserVerifier.java | 50 | ||||
| -rw-r--r-- | src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java | 61 | ||||
| -rw-r--r-- | src/com/p4square/grow/ccb/CustomFieldCache.java | 126 | ||||
| -rw-r--r-- | src/com/p4square/grow/ccb/MonitoredCCBAPI.java | 96 | ||||
| -rw-r--r-- | src/com/p4square/grow/config/Config.java | 30 | ||||
| -rw-r--r-- | src/com/p4square/grow/frontend/AssessmentResultsPage.java | 32 | ||||
| -rw-r--r-- | src/com/p4square/grow/frontend/ChapterCompletePage.java | 27 | ||||
| -rw-r--r-- | src/com/p4square/grow/frontend/GrowFrontend.java | 39 | ||||
| -rw-r--r-- | src/com/p4square/grow/frontend/IntegrationDriver.java | 26 | ||||
| -rw-r--r-- | src/com/p4square/grow/frontend/NewAccountResource.java | 16 | ||||
| -rw-r--r-- | src/com/p4square/grow/frontend/ProgressReporter.java | 30 | ||||
| -rw-r--r-- | src/com/p4square/grow/model/Score.java | 2 | 
14 files changed, 610 insertions, 66 deletions
| diff --git a/src/com/p4square/grow/ccb/CCBProgressReporter.java b/src/com/p4square/grow/ccb/CCBProgressReporter.java new file mode 100644 index 0000000..d2826eb --- /dev/null +++ b/src/com/p4square/grow/ccb/CCBProgressReporter.java @@ -0,0 +1,104 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.*; +import com.p4square.grow.frontend.ProgressReporter; +import com.p4square.grow.model.Score; +import org.apache.log4j.Logger; +import org.restlet.security.User; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; + +/** + * A ProgressReporter which records progress in CCB. + * + * Except not really, because it's not implemented yet. + * This is just a placeholder until ccb-api-client-java has support for updating an individual. + */ +public class CCBProgressReporter implements ProgressReporter { + +    private static final Logger LOG = Logger.getLogger(CCBProgressReporter.class); + +    private static final String GROW_LEVEL = "GrowLevelTrain"; +    private static final String GROW_ASSESSMENT = "GrowLevelAsmnt"; + +    private final CCBAPI mAPI; +    private final CustomFieldCache mCache; + +    public CCBProgressReporter(final CCBAPI api, final CustomFieldCache cache) { +        mAPI = api; +        mCache = cache; +    } + +    @Override +    public void reportAssessmentComplete(final User user, final String level, final Date date, final String results) { +        if (!(user instanceof CCBUser)) { +            throw new IllegalArgumentException("Expected CCBUser but got " + user.getClass().getCanonicalName()); +        } +        final CCBUser ccbuser = (CCBUser) user; + +        updateLevelAndDate(ccbuser, GROW_ASSESSMENT, level, date); +    } + +    @Override +    public void reportChapterComplete(final User user, final String chapter, final Date date) { +        if (!(user instanceof CCBUser)) { +            throw new IllegalArgumentException("Expected CCBUser but got " + user.getClass().getCanonicalName()); +        } +        final CCBUser ccbuser = (CCBUser) user; + +        // Only update the level if it is increasing. +        final CustomPulldownFieldValue currentLevel = ccbuser.getProfile() +                .getCustomPulldownFields().getByLabel(GROW_LEVEL); + +        if (currentLevel != null) { +            if (Score.numericScore(chapter) <= Score.numericScore(currentLevel.getSelection().getLabel())) { +                LOG.info("Not updating level for " + user.getIdentifier() +                        + " because current level (" + currentLevel.getSelection().getLabel() +                        + ") is greater than new level (" + chapter + ")"); +                return; +            } +        } + +        updateLevelAndDate(ccbuser, GROW_LEVEL, chapter, date); +    } + +    private void updateLevelAndDate(final CCBUser user, final String field, final String level, final Date date) { +        boolean modified = false; + +        final UpdateIndividualProfileRequest req = new UpdateIndividualProfileRequest() +                .withIndividualId(user.getProfile().getId()); + +        final CustomField pulldownField = mCache.getIndividualPulldownByLabel(field); +        if (pulldownField != null) { +            final LookupTableType type = LookupTableType.valueOf(pulldownField.getName().toUpperCase()); +            final LookupTableItem item = mCache.getPulldownItemByName(type, level); +            if (item != null) { +                req.withCustomPulldownField(pulldownField.getName(), item.getId()); +                modified = true; +            } +        } + +        final CustomField dateField = mCache.getDateFieldByLabel(field); +        if (dateField != null) { +            req.withCustomDateField(dateField.getName(), date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); +            modified = true; +        } + +        try { +            // Only update if a field exists. +            if (modified) { +                mAPI.updateIndividualProfile(req); +            } + +        } catch (IOException e) { +            LOG.error("updateIndividual failed for " + user.getIdentifier() +                    + ", field " + field +                    + ", level " + level +                    + ", date " + date.toString()); +        } +    } +} diff --git a/src/com/p4square/grow/ccb/CCBUser.java b/src/com/p4square/grow/ccb/CCBUser.java new file mode 100644 index 0000000..7313172 --- /dev/null +++ b/src/com/p4square/grow/ccb/CCBUser.java @@ -0,0 +1,37 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.model.IndividualProfile; +import org.restlet.security.User; + +/** + * CCBUser is an adapter between a CCB IndividualProfile and a Restlet User. + * + * Note: CCBUser prefixes the user's identifier with "CCB-". This is done to + *       ensure the identifier does not collide with identifiers from other + *       systems. + */ +public class CCBUser extends User { + +    private final IndividualProfile mProfile; + +    /** +     * Wrap an IndividualProfile inside a User object. +     * +     * @param profile The CCB IndividualProfile for the user. +     */ +    public CCBUser(final IndividualProfile profile) { +        mProfile = profile; + +        setIdentifier("CCB-" + mProfile.getId()); +        setFirstName(mProfile.getFirstName()); +        setLastName(mProfile.getLastName()); +        setEmail(mProfile.getEmail()); +    } + +    /** +     * @return The IndividualProfile of the user. +     */ +    public IndividualProfile getProfile() { +        return mProfile; +    } +} diff --git a/src/com/p4square/grow/ccb/CCBUserVerifier.java b/src/com/p4square/grow/ccb/CCBUserVerifier.java new file mode 100644 index 0000000..db10b75 --- /dev/null +++ b/src/com/p4square/grow/ccb/CCBUserVerifier.java @@ -0,0 +1,50 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.GetIndividualProfilesRequest; +import com.p4square.ccbapi.model.GetIndividualProfilesResponse; +import org.apache.log4j.Logger; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.security.Verifier; + +/** + * CCBUserVerifier authenticates a user through the CCB individual_profile_from_login_password API. + */ +public class CCBUserVerifier implements Verifier { +    private static final Logger LOG = Logger.getLogger(CCBUserVerifier.class); + +    private final CCBAPI mAPI; + +    public CCBUserVerifier(final CCBAPI api) { +        mAPI = api; +    } + +    @Override +    public int verify(Request request, Response response) { +        if (request.getChallengeResponse() == null) { +            return RESULT_MISSING; // no credentials +        } + +        final String username = request.getChallengeResponse().getIdentifier(); +        final char[] password = request.getChallengeResponse().getSecret(); + +        try { +            GetIndividualProfilesResponse resp = mAPI.getIndividualProfiles( +                    new GetIndividualProfilesRequest().withLoginPassword(username, password)); + +            if (resp.getIndividuals().size() == 1) { +                // Wrap the IndividualProfile up in an User and update the user on the request. +                final CCBUser user = new CCBUser(resp.getIndividuals().get(0)); +                LOG.info("Successfully authenticated " + user.getIdentifier()); +                request.getClientInfo().setUser(user); +                return RESULT_VALID; +            } + +        } catch (Exception e) { +            LOG.error("CCB API Exception: " + e, e); +        } + +        return RESULT_INVALID; // Invalid credentials +    } +} diff --git a/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java b/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java new file mode 100644 index 0000000..fc6148f --- /dev/null +++ b/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java @@ -0,0 +1,61 @@ +package com.p4square.grow.ccb; + +import com.codahale.metrics.MetricRegistry; +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.CCBAPIClient; +import com.p4square.grow.config.Config; +import com.p4square.grow.frontend.IntegrationDriver; +import com.p4square.grow.frontend.ProgressReporter; +import org.restlet.Context; +import org.restlet.security.Verifier; + +import java.net.URI; +import java.net.URISyntaxException; + +/** + * The ChurchCommunityBuilderIntegrationDriver is used to integrate Grow with Church Community Builder. + */ +public class ChurchCommunityBuilderIntegrationDriver implements IntegrationDriver { + +    private final Context mContext; +    private final MetricRegistry mMetricRegistry; +    private final Config mConfig; + +    private final CCBAPI mAPI; + +    private final CCBProgressReporter mProgressReporter; + +    public ChurchCommunityBuilderIntegrationDriver(final Context context) { +        mContext = context; +        mConfig = (Config) context.getAttributes().get("com.p4square.grow.config"); +        mMetricRegistry = (MetricRegistry) context.getAttributes().get("com.p4square.grow.metrics"); + +        try { +            CCBAPI api = new CCBAPIClient(new URI(mConfig.getString("CCBAPIURL", "")), +                                          mConfig.getString("CCBAPIUser", ""), +                                          mConfig.getString("CCBAPIPassword", "")); + +            if (mMetricRegistry != null) { +                api = new MonitoredCCBAPI(api, mMetricRegistry); +            } + +            mAPI = api; + +            final CustomFieldCache cache = new CustomFieldCache(mAPI); +            mProgressReporter = new CCBProgressReporter(mAPI, cache); + +        } catch (URISyntaxException e) { +            throw new RuntimeException(e); +        } +    } + +    @Override +    public Verifier newUserAuthenticationVerifier() { +        return new CCBUserVerifier(mAPI); +    } + +    @Override +    public ProgressReporter getProgressReporter() { +        return mProgressReporter; +    } +} diff --git a/src/com/p4square/grow/ccb/CustomFieldCache.java b/src/com/p4square/grow/ccb/CustomFieldCache.java new file mode 100644 index 0000000..d93e6d9 --- /dev/null +++ b/src/com/p4square/grow/ccb/CustomFieldCache.java @@ -0,0 +1,126 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.*; +import org.apache.log4j.Logger; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * CustomFieldCache maintains an index from custom field labels to names. + */ +public class CustomFieldCache { + +    private static final Logger LOG = Logger.getLogger(CustomFieldCache.class); + +    private final CCBAPI mAPI; + +    private CustomFieldCollection<CustomField> mTextFields; +    private CustomFieldCollection<CustomField> mDateFields; +    private CustomFieldCollection<CustomField> mIndividualPulldownFields; +    private CustomFieldCollection<CustomField> mGroupPulldownFields; + +    private final Map<LookupTableType, Map<String, LookupTableItem>> mItemByNameTable; + +    public CustomFieldCache(final CCBAPI api) { +        mAPI = api; +        mTextFields = new CustomFieldCollection<>(); +        mDateFields = new CustomFieldCollection<>(); +        mIndividualPulldownFields = new CustomFieldCollection<>(); +        mGroupPulldownFields = new CustomFieldCollection<>(); +        mItemByNameTable = new HashMap<>(); +    } + +    public CustomField getTextFieldByLabel(final String label) { +        if (mTextFields.size() == 0) { +            refresh(); +        } +        return mTextFields.getByLabel(label); +    } + +    public CustomField getDateFieldByLabel(final String label) { +        if (mDateFields.size() == 0) { +            refresh(); +        } +        return mDateFields.getByLabel(label); +    } + +    public CustomField getIndividualPulldownByLabel(final String label) { +        if (mIndividualPulldownFields.size() == 0) { +            refresh(); +        } +        return mIndividualPulldownFields.getByLabel(label); +    } + +    public CustomField getGroupPulldownByLabel(final String label) { +        if (mGroupPulldownFields.size() == 0) { +            refresh(); +        } +        return mGroupPulldownFields.getByLabel(label); +    } + +    public LookupTableItem getPulldownItemByName(final LookupTableType type, final String name) { +        Map<String, LookupTableItem> items = mItemByNameTable.get(type); +        if (items == null) { +            if (!cacheLookupTable(type)) { +                return null; +            } +            items = mItemByNameTable.get(type); +        } + +        return items.get(name.toLowerCase()); +    } + +    private synchronized void refresh() { +        try { +            // Get all of the custom fields. +            final GetCustomFieldLabelsResponse resp = mAPI.getCustomFieldLabels(); + +            final CustomFieldCollection<CustomField> newTextFields = new CustomFieldCollection<>(); +            final CustomFieldCollection<CustomField> newDateFields = new CustomFieldCollection<>(); +            final CustomFieldCollection<CustomField> newIndPulldownFields = new CustomFieldCollection<>(); +            final CustomFieldCollection<CustomField> newGrpPulldownFields = new CustomFieldCollection<>(); + +            for (final CustomField field : resp.getCustomFields()) { +                if (field.getName().startsWith("udf_ind_text_")) { +                    newTextFields.add(field); +                } else if (field.getName().startsWith("udf_ind_date_")) { +                    newDateFields.add(field); +                } else if (field.getName().startsWith("udf_ind_pulldown_")) { +                    newIndPulldownFields.add(field); +                } else if (field.getName().startsWith("udf_grp_pulldown_")) { +                    newGrpPulldownFields.add(field); +                } else { +                    LOG.warn("Unknown custom field type " + field.getName()); +                } +            } + +            this.mTextFields = newTextFields; +            this.mDateFields = newDateFields; +            this.mIndividualPulldownFields = newIndPulldownFields; +            this.mGroupPulldownFields = newGrpPulldownFields; + +        } catch (IOException e) { +            // Error fetching labels. +            LOG.error("Error fetching custom fields: " + e.getMessage(), e); +        } +    } + +    private synchronized boolean cacheLookupTable(final LookupTableType type) { +        try { +            final GetLookupTableResponse resp = mAPI.getLookupTable(new GetLookupTableRequest().withType(type)); +            mItemByNameTable.put(type, resp.getItems().stream().collect( +                    Collectors.toMap(item -> item.getName().toLowerCase(), Function.identity()))); +            return true; + +        } catch (IOException e) { +            LOG.error("Exception caching lookup table of type " + type, e); +        } + +        return false; +    } +} diff --git a/src/com/p4square/grow/ccb/MonitoredCCBAPI.java b/src/com/p4square/grow/ccb/MonitoredCCBAPI.java new file mode 100644 index 0000000..43b6433 --- /dev/null +++ b/src/com/p4square/grow/ccb/MonitoredCCBAPI.java @@ -0,0 +1,96 @@ +package com.p4square.grow.ccb; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.*; + +import java.io.IOException; + +/** + * MonitoredCCBAPI is a CCBAPI decorator which records metrics for each API call. + */ +public class MonitoredCCBAPI implements CCBAPI { + +    private final CCBAPI mAPI; +    private final MetricRegistry mMetricRegistry; + +    public MonitoredCCBAPI(final CCBAPI api, final MetricRegistry metricRegistry) { +        if (api == null) { +            throw new IllegalArgumentException("api must not be null."); +        } +        mAPI = api; + +        if (metricRegistry == null) { +            throw new IllegalArgumentException("metricRegistry must not be null."); +        } +        mMetricRegistry = metricRegistry; +    } + +    @Override +    public GetCustomFieldLabelsResponse getCustomFieldLabels() throws IOException { +        final Timer.Context timer = mMetricRegistry.timer("CCBAPI.getCustomFieldLabels.time").time(); +        boolean success = false; +        try { +            final GetCustomFieldLabelsResponse resp = mAPI.getCustomFieldLabels(); +            success = true; +            return resp; +        } finally { +            timer.stop(); +            mMetricRegistry.counter("CCBAPI.getCustomFieldLabels.success").inc(success ? 1 : 0); +            mMetricRegistry.counter("CCBAPI.getCustomFieldLabels.failure").inc(!success ? 1 : 0); +        } +    } + +    @Override +    public GetLookupTableResponse getLookupTable(final GetLookupTableRequest request) throws IOException { +        final Timer.Context timer = mMetricRegistry.timer("CCBAPI.getLookupTable.time").time(); +        boolean success = false; +        try { +            final GetLookupTableResponse resp = mAPI.getLookupTable(request); +            success = true; +            return resp; +        } finally { +            timer.stop(); +            mMetricRegistry.counter("CCBAPI.getLookupTable.success").inc(success ? 1 : 0); +            mMetricRegistry.counter("CCBAPI.getLookupTable.failure").inc(!success ? 1 : 0); +        } +    } + +    @Override +    public GetIndividualProfilesResponse getIndividualProfiles(GetIndividualProfilesRequest request) +            throws IOException { +        final Timer.Context timer = mMetricRegistry.timer("CCBAPI.getIndividualProfiles").time(); +        boolean success = false; +        try { +            final GetIndividualProfilesResponse resp = mAPI.getIndividualProfiles(request); +            mMetricRegistry.counter("CCBAPI.getIndividualProfiles.count").inc(resp.getIndividuals().size()); +            success = true; +            return resp; +        } finally { +            timer.stop(); +            mMetricRegistry.counter("CCBAPI.getIndividualProfiles.success").inc(success ? 1 : 0); +            mMetricRegistry.counter("CCBAPI.getIndividualProfiles.failure").inc(!success ? 1 : 0); +        } +    } + +    @Override +    public UpdateIndividualProfileResponse updateIndividualProfile(UpdateIndividualProfileRequest request) throws IOException { +        final Timer.Context timer = mMetricRegistry.timer("CCBAPI.updateIndividualProfile").time(); +        boolean success = false; +        try { +            final UpdateIndividualProfileResponse resp = mAPI.updateIndividualProfile(request); +            success = true; +            return resp; +        } finally { +            timer.stop(); +            mMetricRegistry.counter("CCBAPI.updateIndividualProfile.success").inc(success ? 1 : 0); +            mMetricRegistry.counter("CCBAPI.updateIndividualProfile.failure").inc(!success ? 1 : 0); +        } +    } + +    @Override +    public void close() throws IOException { +        mAPI.close(); +    } +} diff --git a/src/com/p4square/grow/config/Config.java b/src/com/p4square/grow/config/Config.java index e89990b..2fc2ea3 100644 --- a/src/com/p4square/grow/config/Config.java +++ b/src/com/p4square/grow/config/Config.java @@ -35,12 +35,27 @@ public class Config {      private Properties mProperties;      /** -     * Construct a new Config object. Domain defaults to prod. +     * Construct a new Config object. +     * +     * Sets the domain to the value of the system property CONFIG_DOMAIN, if present. +     * If the system property is not set then the environment variable CONFIG_DOMAIN is checked. +     * If neither are set the domain defaults to prod.       */      public Config() { -        mDomain = "prod"; -        mProperties = new Properties(); +        // Check the command line for a domain property. +        mDomain = System.getProperty("CONFIG_DOMAIN"); + +        // If the domain was not set with a property, check for an environment variable. +        if (mDomain == null) { +            mDomain = System.getenv("CONFIG_DOMAIN"); +        } + +        // If neither were set, default to prod +        if (mDomain == null) { +            mDomain = "prod"; +        } +        mProperties = new Properties();      }      /** @@ -62,7 +77,7 @@ public class Config {      /**       * Load properties from a file. -     * Any exceptions are logged and suppressed. +     * Any exception are logged and suppressed.       */      public void updateConfig(String propertyFilename) {          final File propFile = new File(propertyFilename); @@ -114,6 +129,13 @@ public class Config {              return result;          } +        // Environment variables can also override configs +        result = System.getenv(key); +        if (result != null) { +            LOG.debug("Reading System.getenv(" + key + "). Got result = { " + result + " }"); +            return result; +        } +          final String domainKey = mDomain + "." + key;          result = mProperties.getProperty(domainKey);          if (result != null) { diff --git a/src/com/p4square/grow/frontend/AssessmentResultsPage.java b/src/com/p4square/grow/frontend/AssessmentResultsPage.java index 2035ce8..f1c924b 100644 --- a/src/com/p4square/grow/frontend/AssessmentResultsPage.java +++ b/src/com/p4square/grow/frontend/AssessmentResultsPage.java @@ -7,6 +7,8 @@ package com.p4square.grow.frontend;  import java.util.Date;  import java.util.Map; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.p4square.f1oauth.FellowshipOneIntegrationDriver;  import freemarker.template.Template;  import org.restlet.data.MediaType; @@ -29,6 +31,7 @@ import com.p4square.f1oauth.F1User;  import com.p4square.grow.config.Config;  import com.p4square.grow.provider.JsonEncodedProvider; +import org.restlet.security.User;  /**   * This page fetches the user's final score and displays the transitional page between @@ -102,29 +105,18 @@ public class AssessmentResultsPage extends FreeMarkerPageResource {      }      private void publishScoreInF1(Map results) { -        if (!(getRequest().getClientInfo().getUser() instanceof F1User)) { -            // Only useful if the user is from F1. -            return; -        } +        final ProgressReporter reporter = mGrowFrontend.getThirdPartyIntegrationFactory().getProgressReporter(); -        F1User user = (F1User) getRequest().getClientInfo().getUser(); +        try { +            final User user = getRequest().getClientInfo().getUser(); +            final String level = results.get("result").toString(); +            final Date completionDate = new Date(); +            final String data = JsonEncodedProvider.MAPPER.writeValueAsString(results); -        // Update the attribute. -        String attributeName = "Assessment Complete - " + results.get("result"); +            reporter.reportAssessmentComplete(user, level, completionDate, data); -        try { -            Attribute attribute = new Attribute(attributeName); -            attribute.setStartDate(new Date()); -            attribute.setComment(JsonEncodedProvider.MAPPER.writeValueAsString(results)); - -            F1API f1 = mGrowFrontend.getF1Access().getAuthenticatedApi(user); -            if (!f1.addAttribute(user.getIdentifier(), attribute)) { -                LOG.error("addAttribute failed for " + user.getIdentifier()  -                        + " with attribute " + attributeName); -            } -        } catch (Exception e) { -            LOG.error("addAttribute failed for " + user.getIdentifier()  -                    + " with attribute " + attributeName, e); +        } catch (JsonProcessingException e) { +            LOG.error("Failed to generate json " + e.getMessage(), e);          }      } diff --git a/src/com/p4square/grow/frontend/ChapterCompletePage.java b/src/com/p4square/grow/frontend/ChapterCompletePage.java index f07a870..35abc43 100644 --- a/src/com/p4square/grow/frontend/ChapterCompletePage.java +++ b/src/com/p4square/grow/frontend/ChapterCompletePage.java @@ -7,6 +7,7 @@ package com.p4square.grow.frontend;  import java.util.Date;  import java.util.Map; +import com.p4square.f1oauth.FellowshipOneIntegrationDriver;  import freemarker.template.Template;  import org.restlet.data.MediaType; @@ -31,6 +32,7 @@ import com.p4square.grow.config.Config;  import com.p4square.grow.model.TrainingRecord;  import com.p4square.grow.provider.Provider;  import com.p4square.grow.provider.TrainingRecordProvider; +import org.restlet.security.User;  /**   * This resource displays the transitional page between chapters. @@ -158,29 +160,12 @@ public class ChapterCompletePage extends FreeMarkerPageResource {      }      private void assignAttribute() { -        if (!(getRequest().getClientInfo().getUser() instanceof F1User)) { -            // Only useful if the user is from F1. -            return; -        } - -        F1User user = (F1User) getRequest().getClientInfo().getUser(); +        final ProgressReporter reporter = mGrowFrontend.getThirdPartyIntegrationFactory().getProgressReporter(); -        // Update the attribute. -        String attributeName = "Training Complete - " + mChapter; - -        try { -            Attribute attribute = new Attribute(attributeName); -            attribute.setStartDate(new Date()); +        final User user = getRequest().getClientInfo().getUser(); +        final Date completionDate = new Date(); -            F1API f1 = mGrowFrontend.getF1Access().getAuthenticatedApi(user); -            if (!f1.addAttribute(user.getIdentifier(), attribute)) { -                LOG.error("addAttribute failed for " + user.getIdentifier()  -                        + " with attribute " + attributeName); -            } -        } catch (Exception e) { -            LOG.error("addAttribute failed for " + user.getIdentifier()  -                    + " with attribute " + attributeName, e); -        } +        reporter.reportChapterComplete(user, mChapter, completionDate);      }      /** diff --git a/src/com/p4square/grow/frontend/GrowFrontend.java b/src/com/p4square/grow/frontend/GrowFrontend.java index 1d221cc..b5f62fb 100644 --- a/src/com/p4square/grow/frontend/GrowFrontend.java +++ b/src/com/p4square/grow/frontend/GrowFrontend.java @@ -6,15 +6,12 @@ package com.p4square.grow.frontend;  import java.io.File;  import java.io.IOException; - -import java.util.Arrays; -import java.util.UUID; +import java.lang.reflect.Constructor;  import freemarker.template.Template;  import org.restlet.Application;  import org.restlet.Component; -import org.restlet.Client;  import org.restlet.Context;  import org.restlet.Restlet;  import org.restlet.data.Protocol; @@ -32,13 +29,11 @@ import com.p4square.fmfacade.FreeMarkerPageResource;  import com.p4square.grow.config.Config; -import com.p4square.f1oauth.F1Access; -import com.p4square.f1oauth.SecondPartyVerifier; -  import com.p4square.restlet.metrics.MetricRouter;  import com.p4square.session.SessionCheckingAuthenticator;  import com.p4square.session.SessionCreatingAuthenticator; +import org.restlet.security.Verifier;  /**   * This is the Restlet Application implementing the Grow project front-end. @@ -54,7 +49,7 @@ public class GrowFrontend extends FMFacade {      private final Config mConfig;      private final MetricRegistry mMetricRegistry; -    private F1Access mHelper; +    private IntegrationDriver mIntegrationFactory;      public GrowFrontend() {          this(new Config(), new MetricRegistry()); @@ -81,20 +76,26 @@ public class GrowFrontend extends FMFacade {                      FreeMarkerPageResource.baseRootObject(getContext(), this));          } +        getContext().getAttributes().put("com.p4square.grow.config", mConfig); +        getContext().getAttributes().put("com.p4square.grow.metrics", mMetricRegistry); +          super.start();      } -    synchronized F1Access getF1Access() { -        if (mHelper == null) { -            mHelper = new F1Access(getContext(), mConfig.getString("f1ConsumerKey", ""), -                    mConfig.getString("f1ConsumerSecret", ""), -                    mConfig.getString("f1BaseUrl", "staging.fellowshiponeapi.com"), -                    mConfig.getString("f1ChurchCode", "pfseawa"), -                    F1Access.UserType.WEBLINK); -            mHelper.setMetricRegistry(mMetricRegistry); +    public synchronized IntegrationDriver getThirdPartyIntegrationFactory() { +        if (mIntegrationFactory == null) { +            final String driverClassName = getConfig().getString("integrationDriver", +                                                                 "com.p4square.f1oauth.FellowshipOneIntegrationDriver"); +            try { +                Class<?> clazz = Class.forName(driverClassName); +                Constructor<?> constructor = clazz.getConstructor(Context.class); +                mIntegrationFactory = (IntegrationDriver) constructor.newInstance(getContext()); +            } catch (Exception e) { +                LOG.error("Failed to instantiate IntegrationDriver " + driverClassName); +            }          } -        return mHelper; +        return mIntegrationFactory;      }      @Override @@ -141,8 +142,8 @@ public class GrowFrontend extends FMFacade {          SessionCheckingAuthenticator sessionChk = new SessionCheckingAuthenticator(context, true);          // This is used to authenticate the user -        SecondPartyVerifier f1Verifier = new SecondPartyVerifier(context, getF1Access()); -        LoginFormAuthenticator loginAuth = new LoginFormAuthenticator(context, false, f1Verifier); +        Verifier verifier = getThirdPartyIntegrationFactory().newUserAuthenticationVerifier(); +        LoginFormAuthenticator loginAuth = new LoginFormAuthenticator(context, false, verifier);          loginAuth.setLoginFormUrl(loginPage);          loginAuth.setLoginPostUrl(loginPost);          loginAuth.setDefaultPage(defaultPage); diff --git a/src/com/p4square/grow/frontend/IntegrationDriver.java b/src/com/p4square/grow/frontend/IntegrationDriver.java new file mode 100644 index 0000000..b9c3508 --- /dev/null +++ b/src/com/p4square/grow/frontend/IntegrationDriver.java @@ -0,0 +1,26 @@ +package com.p4square.grow.frontend; + +import org.restlet.security.Verifier; + +/** + * An IntegrationDriver is used to create implementations of various objects + * used to integration Grow with a particular Church Management System. + */ +public interface IntegrationDriver { + +    /** +     * Create a new Restlet Verifier to authenticate users when they login to the site. +     * +     * @return A Verifier. +     */ +    Verifier newUserAuthenticationVerifier(); + +    /** +     * Return a ProgressReporter for this Church Management System. +     * +     * The ProgressReporter should be thread-safe. +     * +     * @return The ProgressReporter. +     */ +    ProgressReporter getProgressReporter(); +} diff --git a/src/com/p4square/grow/frontend/NewAccountResource.java b/src/com/p4square/grow/frontend/NewAccountResource.java index 54c1790..5c13017 100644 --- a/src/com/p4square/grow/frontend/NewAccountResource.java +++ b/src/com/p4square/grow/frontend/NewAccountResource.java @@ -6,12 +6,12 @@ package com.p4square.grow.frontend;  import java.util.Map; +import com.p4square.f1oauth.FellowshipOneIntegrationDriver;  import freemarker.template.Template;  import org.restlet.data.Form;  import org.restlet.data.MediaType;  import org.restlet.data.Status; -import org.restlet.resource.ServerResource;  import org.restlet.representation.Representation;  import org.restlet.representation.StringRepresentation;  import org.restlet.ext.freemarker.TemplateRepresentation; @@ -44,7 +44,14 @@ public class NewAccountResource extends FreeMarkerPageResource {          super.doInit();          mGrowFrontend = (GrowFrontend) getApplication(); -        mHelper = mGrowFrontend.getF1Access(); + +        final IntegrationDriver driver = mGrowFrontend.getThirdPartyIntegrationFactory(); +        if (driver instanceof FellowshipOneIntegrationDriver) { +            mHelper = ((FellowshipOneIntegrationDriver) driver).getF1Access(); +        } else { +            LOG.error("NewAccountResource only works with F1!"); +            mHelper = null; +        }          mErrorMessage = ""; @@ -83,6 +90,11 @@ public class NewAccountResource extends FreeMarkerPageResource {      @Override      protected Representation post(Representation rep) { +        if (mHelper == null) { +            mErrorMessage += "F1 support is not enabled! "; +            return get(); +        } +          Form form = new Form(rep);          String firstname = form.getFirstValue("firstname"); diff --git a/src/com/p4square/grow/frontend/ProgressReporter.java b/src/com/p4square/grow/frontend/ProgressReporter.java new file mode 100644 index 0000000..2f36832 --- /dev/null +++ b/src/com/p4square/grow/frontend/ProgressReporter.java @@ -0,0 +1,30 @@ +package com.p4square.grow.frontend; + +import org.restlet.security.User; + +import java.util.Date; + +/** + * A ProgressReporter is used to record a User's progress in a Church Management System. + */ +public interface ProgressReporter { + +    /** +     * Report that the User completed the assessment. +     * +     * @param user The user who completed the assessment. +     * @param level The assessment level. +     * @param date The completion date. +     * @param results Result information (e.g. json of the results). +     */ +    void reportAssessmentComplete(User user, String level, Date date, String results); + +    /** +     * Report that the User completed the chapter. +     * +     * @param user The user who completed the chapter. +     * @param chapter The chapter completed. +     * @param date The completion date. +     */ +    void reportChapterComplete(User user, String chapter, Date date); +} diff --git a/src/com/p4square/grow/model/Score.java b/src/com/p4square/grow/model/Score.java index 82f26c9..031c309 100644 --- a/src/com/p4square/grow/model/Score.java +++ b/src/com/p4square/grow/model/Score.java @@ -17,6 +17,8 @@ public class Score {       *  numericScore(x.toString()) <= x.getScore()       */      public static double numericScore(String score) { +        score = score.toLowerCase(); +          if ("teacher".equals(score)) {              return 3.5;          } else if ("disciple".equals(score)) { | 
