001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.request.resource;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Locale;
023
024import org.apache.wicket.Application;
025import org.apache.wicket.core.util.lang.WicketObjects;
026import org.apache.wicket.markup.head.HeaderItem;
027import org.apache.wicket.util.io.IClusterable;
028import org.apache.wicket.util.lang.Args;
029import org.apache.wicket.util.lang.Objects;
030import org.danekja.java.util.function.serializable.SerializableSupplier;
031
032/**
033 * Reference to a resource. Can be used to reference global resources.
034 * <p>
035 * Even though resource reference is just a factory for resources, it still needs to be identified
036 * by a globally unique identifier, combination of <code>scope</code> and <code>name</code>. Those
037 * are used to generate URLs for resource references. <code>locale</code>, <code>style</code> and
038 * <code>variation</code> are optional fields to allow having specific references for individual
039 * locales, styles and variations.
040 * 
041 * @author Matej Knopp
042 * @author Juergen Donnerstag
043 */
044public abstract class ResourceReference implements IClusterable
045{
046        private static final long serialVersionUID = 1L;
047
048        private final Key data;
049
050        /**
051         * Creates new {@link ResourceReference} instance.
052         * 
053         * @param key
054         *            The data making up the resource reference
055         */
056        public ResourceReference(final Key key)
057        {
058                Args.notNull(key, "key");
059
060                data = key;
061        }
062
063        /**
064         * Creates new {@link ResourceReference} instance.
065         * 
066         * @param scope
067         *            mandatory parameter
068         * @param name
069         *            mandatory parameter
070         * @param locale
071         *            resource locale
072         * @param style
073         *            resource style
074         * @param variation
075         *            resource variation
076         */
077        public ResourceReference(Class<?> scope, String name, Locale locale, String style,
078                String variation)
079        {
080                Args.notNull(scope, "scope");
081                Args.notNull(name, "name");
082
083                data = new Key(scope.getName(), name, locale, style, variation);
084        }
085
086        /**
087         * Creates new {@link ResourceReference} instance.
088         * 
089         * @param scope
090         *            mandatory parameter
091         * @param name
092         *            mandatory parameter
093         */
094        public ResourceReference(Class<?> scope, String name)
095        {
096                this(scope, name, null, null, null);
097        }
098
099        /**
100         * Construct.
101         * 
102         * @param name
103         *            resource name
104         */
105        public ResourceReference(String name)
106        {
107                this(Application.class, name, null, null, null);
108        }
109
110        /**
111         * @return Gets the data making up the resource reference. They'll be use by
112         *         ResourceReferenceRegistry to make up the key under which the resource reference gets
113         *         stored.
114         */
115        public final Key getKey()
116        {
117                return data;
118        }
119
120        /**
121         * @return name
122         */
123        public String getName()
124        {
125                return data.getName();
126        }
127
128        /**
129         * returns extension of the resource reference
130         * 
131         * @return extension of the resource's name in lower-case or <code>null</code> if there is no
132         *         extension
133         */
134        public final String getExtension()
135        {
136                String name = getName();
137
138                final int queryAt = name.indexOf('?');
139
140                // remove query string part
141                if (queryAt != -1)
142                {
143                        name = name.substring(0, queryAt);
144                }
145
146                // get start of extension
147                final int extPos = name.lastIndexOf('.');
148
149                if (extPos == -1)
150                {
151                        return null;
152                }
153
154                // return extension
155                return name.substring(extPos + 1).toLowerCase(Locale.ROOT);
156        }
157
158        /**
159         * @return scope
160         */
161        public Class<?> getScope()
162        {
163                return WicketObjects.resolveClass(data.getScope());
164        }
165
166        /**
167         * @return locale
168         */
169        public Locale getLocale()
170        {
171                return data.getLocale();
172        }
173
174        /**
175         * @return style
176         */
177        public String getStyle()
178        {
179                return data.getStyle();
180        }
181
182        /**
183         * @return variation
184         */
185        public String getVariation()
186        {
187                return data.getVariation();
188        }
189
190        /**
191         * Can be used to disable registering certain resource references in
192         * {@link ResourceReferenceRegistry}.
193         * 
194         * @return <code>true</code> if this reference can be registered, <code>false</code> otherwise.
195         */
196        public boolean canBeRegistered()
197        {
198                return true;
199        }
200
201        /**
202         * @see java.lang.Object#equals(java.lang.Object)
203         */
204        @Override
205        public boolean equals(Object obj)
206        {
207                if (this == obj)
208                {
209                        return true;
210                }
211                if (obj instanceof ResourceReference == false)
212                {
213                        return false;
214                }
215                ResourceReference that = (ResourceReference)obj;
216                return Objects.equal(data, that.data);
217        }
218
219        /**
220         * @see java.lang.Object#hashCode()
221         */
222        @Override
223        public int hashCode()
224        {
225                return data.hashCode();
226        }
227
228        /**
229         * Returns the resource.
230         * 
231         * @return resource instance
232         */
233        public abstract IResource getResource();
234
235        /**
236         * Allows to specify which locale, style and variation values will the generated URL for this
237         * resource reference have.
238         * 
239         * @return url attributes
240         */
241        public UrlAttributes getUrlAttributes()
242        {
243                return new UrlAttributes(getLocale(), getStyle(), getVariation());
244        }
245        
246        /**
247         * Factory method to build a resource reference that uses the provided supplier to return
248         * the resource.
249         * 
250         * @param name
251         *                              The name to use with the resource
252         * @param resourceSupplier
253         *                              Lambda supplier to build the resource
254         * @return the new resource reference
255         */
256        public static final ResourceReference of(String name, SerializableSupplier<IResource> resourceSupplier)
257        {
258                return new LambdaResourceReference(name, resourceSupplier);
259        }
260
261        /**
262         * Factory method to build a resource reference that uses the provided supplier to return
263         * the resource.
264         * 
265         * @param key
266         *                              The {@link Key} to use with the resource
267         * @param resourceSupplier
268         *                              Lambda supplier to build the resource
269         * @return  the new resource reference
270         */
271        public static final ResourceReference of(Key key, SerializableSupplier<IResource> resourceSupplier)
272        {
273                return new LambdaResourceReference(key, resourceSupplier);
274        }
275
276        public static final class LambdaResourceReference extends ResourceReference
277        {
278                private static final long serialVersionUID = 1826862147241009289L;
279                
280                final SerializableSupplier<IResource> resourceBuilder;
281
282                public LambdaResourceReference(String name, SerializableSupplier<IResource> resourceBuilder) 
283                {
284                        super(name);
285                        this.resourceBuilder = Args.notNull(resourceBuilder, "resourceBuilder");
286                }
287
288                public LambdaResourceReference(Key key, SerializableSupplier<IResource> resourceBuilder) 
289                {
290                        super(key);
291                        this.resourceBuilder = Args.notNull(resourceBuilder, "resourceBuilder");
292                }
293
294                @Override
295                public IResource getResource() 
296                {
297                        return resourceBuilder.get();
298                }
299        }
300        
301        /**
302         * @see ResourceReference#getUrlAttributes()
303         * 
304         * @author Matej Knopp
305         */
306        public static class UrlAttributes
307        {
308                private final Locale locale;
309                private final String style;
310                private final String variation;
311
312                /**
313                 * Construct.
314                 * 
315                 * @param locale
316                 *            resource locale
317                 * @param style
318                 *            resource style
319                 * @param variation
320                 *            resource variation
321                 */
322                public UrlAttributes(Locale locale, String style, String variation)
323                {
324                        this.locale = locale;
325                        this.style = style;
326                        this.variation = variation;
327                }
328
329                /**
330                 * @return locale
331                 */
332                public Locale getLocale()
333                {
334                        return locale;
335                }
336
337                /**
338                 * @return style
339                 */
340                public String getStyle()
341                {
342                        return style;
343                }
344
345                /**
346                 * @return variation
347                 */
348                public String getVariation()
349                {
350                        return variation;
351                }
352
353                @Override
354                public boolean equals(Object obj)
355                {
356                        if (this == obj)
357                        {
358                                return true;
359                        }
360                        if (obj instanceof UrlAttributes == false)
361                        {
362                                return false;
363                        }
364                        UrlAttributes that = (UrlAttributes)obj;
365                        return Objects.equal(getLocale(), that.getLocale()) &&
366                                Objects.equal(getStyle(), that.getStyle()) &&
367                                Objects.equal(getVariation(), that.getVariation());
368                }
369
370                @Override
371                public int hashCode() {
372                        // Not using `Objects.hash` for performance reasons
373                        int result = locale != null ? locale.hashCode() : 0;
374                        result = 31 * result + (style != null ? style.hashCode() : 0);
375                        result = 31 * result + (variation != null ? variation.hashCode() : 0);
376                        return result;
377                }
378
379                /**
380                 * @see java.lang.Object#toString()
381                 */
382                @Override
383                public String toString()
384                {
385                        return "locale: " + locale + "; style: " + style + "; variation: " + variation;
386                }
387        }
388
389        /**
390         * A (re-usable) data store for all relevant ResourceReference data
391         */
392        public static class Key implements Serializable
393        {
394                private static final long serialVersionUID = 1L;
395
396                private final String scope;
397                private final String name;
398                private final Locale locale;
399                private final String style;
400                private final String variation;
401
402                /**
403                 * Construct.
404                 * 
405                 * @param reference
406                 *            resource reference
407                 */
408                public Key(final ResourceReference reference)
409                {
410                        this(reference.getScope().getName(), reference.getName(), reference.getLocale(),
411                                reference.getStyle(), reference.getVariation());
412                }
413
414                /**
415                 * Construct.
416                 * 
417                 * @param scope
418                 *            resource scope
419                 * @param name
420                 *            resource name
421                 * @param locale
422                 *            resource locale
423                 * @param style
424                 *            resource style
425                 * @param variation
426                 *            resource variation
427                 */
428                public Key(final String scope, final String name, final Locale locale, final String style,
429                        final String variation)
430                {
431                        Args.notNull(scope, "scope");
432                        Args.notNull(name, "name");
433
434                        this.scope = scope.intern();
435                        this.name = name.intern();
436                        this.locale = locale;
437                        this.style = style != null ? style.intern() : null;
438                        this.variation = variation != null ? variation.intern() : null;
439                }
440
441                @Override
442                public boolean equals(final Object obj)
443                {
444                        if (this == obj)
445                        {
446                                return true;
447                        }
448                        if (obj instanceof Key == false)
449                        {
450                                return false;
451                        }
452                        Key that = (Key)obj;
453                        return Objects.equal(scope, that.scope) && //
454                                Objects.equal(name, that.name) && //
455                                Objects.equal(locale, that.locale) && //
456                                Objects.equal(style, that.style) && //
457                                Objects.equal(variation, that.variation);
458                }
459
460                @Override
461                public int hashCode() {
462                        int result = scope != null ? scope.hashCode() : 0;
463                        result = 31 * result + (name != null ? name.hashCode() : 0);
464                        result = 31 * result + (locale != null ? locale.hashCode() : 0);
465                        result = 31 * result + (style != null ? style.hashCode() : 0);
466                        result = 31 * result + (variation != null ? variation.hashCode() : 0);
467                        return result;
468                }
469
470                /**
471                 * Gets scope.
472                 * 
473                 * @return scope
474                 */
475                public final String getScope()
476                {
477                        return scope;
478                }
479
480                /**
481                 * @return Assuming scope ist a fully qualified class name, than get the associated class
482                 */
483                public final Class<?> getScopeClass()
484                {
485                        return WicketObjects.resolveClass(scope);
486                }
487
488                /**
489                 * Gets name.
490                 * 
491                 * @return name
492                 */
493                public final String getName()
494                {
495                        return name;
496                }
497
498                /**
499                 * Gets locale.
500                 * 
501                 * @return locale
502                 */
503                public final Locale getLocale()
504                {
505                        return locale;
506                }
507
508                /**
509                 * Gets style.
510                 * 
511                 * @return style
512                 */
513                public final String getStyle()
514                {
515                        return style;
516                }
517
518                /**
519                 * Gets variation.
520                 * 
521                 * @return variation
522                 */
523                public final String getVariation()
524                {
525                        return variation;
526                }
527
528                /**
529                 * @see java.lang.Object#toString()
530                 */
531                @Override
532                public String toString()
533                {
534                        return "scope: " + scope + "; name: " + name + "; locale: " + locale + "; style: " +
535                                style + "; variation: " + variation;
536                }
537        }
538
539        @Override
540        public String toString()
541        {
542                return data.toString();
543        }
544
545        /**
546         * @return the resources this ResourceReference depends on.
547         */
548        public List<HeaderItem> getDependencies()
549        {
550                return new ArrayList<>();
551        }
552}